Rust笔记:所有权

Posted by keys961 on January 12, 2020

0. Intro

最近考虑自己项目用Rust重写的可能性,所以这里记录一下Rust的特性,以防止自己忘记。

Rust上手曲线有点陡峭,因为它和其它语言的设计思路很不一样。

这里就记录一些Rust特有的特性,首先就是“所有权”问题(想起了C++的std::move

Rust没有GC,又不像C/C++一样需要手动释放内存(这里我们只讨论堆内存),所以有了所有权概念。

假如有一个变量s,它指向堆内的一块内存(假如是字符串),则:

  • 当变量s在作用域内,则其有指向内容的所有权
  • 当变量s在作用域外,则其没有指向内存的所有权,且指向的内容会被释放
1
2
3
4
5
6
7
fn ownership() {
    {
    	let s = String::from("str"); // Create s -> "str" (in heap), s owns "str"
        // ...
    }
    // Here s doesn't own "str", and "str" is freed
}

1. 移动与拷贝

1.1. 移动

Rust和其它语言不同——赋值默认是移动语义。

移动之后,所有权被移交出去,之前的变量就不可访问了:

1
2
3
let s = String::from("str");
let p = s; // Ownership moved from s to p
println!("{}", s); // Compiling error!

1.2. 拷贝

当数据结构实现了Copy的trait(即数据结构可存储在栈上)后,其赋值默认就是拷贝,例如整数、字符、浮点数、布尔值。

1
2
3
let s = 1;
let p = s; // Value 1 is copied to p (copied in compilation time)
println!("{}", s); // OK

实现Copy后,则不能实现Drop

2. 函数与所有权

函数的参数传递和结果返回,也满足1.1.的语义,即:

  • 默认移动语义
  • 实现Copy默认拷贝

例如一般情况(内容存在堆中):

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
    let v = String::from("str"); // v owns "str"
    let q = move_ownership(v); // 1. v moves "str" to arg
    
    println!("{}", q); // OK. q owns "str"
    println!("{}", v); // Error, v doesn't own "str"
}

fn move_ownership(arg: String) -> String {
    let p = arg; // 2. arg moves "str" to p
    return p; // 3. p moves "str" to q
}

而栈中的数据,上述类似的调用是拷贝语义,编译不会出错。

3. 引用

为了让1.2.的情况编译通过,Rust引入引用概念,意思就是“我能使用它,但我不拥有它”,类似于借用的概念。

引用和C++一样,使用&;而解引用和C++指针类似,使用*

当然,引用也分可变和不可变:

  • 不可变引用:默认,使用&,引用只读
  • 可变引用:使用&mut,引用可写
1
2
3
4
5
6
7
8
9
10
11
fn main() {
    let v = String::from("str"); // v owns "str"
    let p = reference(&v);// "str" is borrowed/referenced by arg, but v still owns it
    println!("{}", v); // OK, v still owns "str"
    println!("{}", p); 
}

fn reference(arg: &String) -> usize {
    // arg.push_str("append"); // Error: ref arg is immutable, cannot write
    return arg.len();
}
1
2
3
4
5
6
7
8
9
fn main() {
    let mut v = String::from("str"); // v owns "str"
    reference_mut(&mut v); // "str" is borrowed/referenced by arg, but v still owns it
    println!("{}", v);
}

fn reference_mut(arg: &mut String) {
    arg.push_str("ssxsx"); // OK: arg is mutable, can write
}

但是引用的使用,需要注意下面几点:

  • 不能通过解引用来移动:因为引用不拥有对应的内容,只有使用权。下面的例子会编译错误:

    1
    2
    3
    
    fn move_reference(arg: &String) -> String {
        return *arg; // Error: arg doesn't own the content, cannot move.
    }
    
  • 不要返回悬垂引用:这会导致不可预计的后果。如:

    • 源变量指向的内容被释放。如下面的例子,会编译错误:
    1
    2
    3
    4
    5
    6
    7
    8
    
    fn main() {
        let ref: &String = dangling_ref(); // Error: ref is dangling, the content "str" is freed
    }
      
    fn dangling_ref() -> &String {
        let str = String::from("str"); // str owns "str"
        return &str; // Return the reference of str
    } // Function return, str doesn't own "str", "str" is freed.
    
    • 源变量被移动。如下面的例子,也会编译错误
    1
    2
    3
    4
    5
    6
    
    fn main() {
        let src = String::from("src"); // src owns "src"
        let ref = &src; // ref references to src
        let new_src = src; // src is moved to new_src
        println!("{}", ref); // Error: ref references src that is moved
    }
    
  • 可变引用与不可变引用的冲突:Rust引用类似读写锁,在编译期就避免数据竞争,即一个变量不可以

    • 拥有多个可变引用
    • 可变引用和不可变引用共存

4. Slice

Slice是集合中的一个部分,它是一种特殊且不可变的引用没有所有权

所以它不能移动,如不能let s = *slice;,这里slice只是一个Slice/引用,只有使用权,不能获取所有权

Slice可作用于字符串、数组等数据结构,语法是:&arr[lower..upper]注意不要忘记&,因为Slice是引用。

1
2
3
4
// example
let str = String::from("stringexample");
let slice = &str[0..4]; // A slice (reference) with content "stri"
let not_valid = *slice; // Error: Cannot move/dereference, because slice doesn't have ownerships

其中lower..upper会返回一个Rangelowerupper可省略。Range左闭右开。

1
2
3
4
// example
for i in (1..10) {
    println!("{}", i); // This will print 1 to 9
}

字面值也是Slice

字符串的字面值也是一个Slice,它的类型是&str,即一个引用。所以,字面值是不能被修改,且不能被移动(因为接收字符串字面值的变量不拥有它的所有权)。