Rust所有权编译错误解析

所有权

Rust所有权三原则

  1. Each value in Rust has a variable that’s called its owner. (Rust中每一个变量都有一个所有者。)
  2. There can only be one owner at a time.(在任一时刻,所有者有且仅有一个。)
  3. When the owner goes out of scope, the value will be dropped.(当所有者离开其作用域后,它所拥有的数据会被释放。)

所有权相关编译错误

错误1

代码段1:

1
2
3
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);

编译结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:5:28
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 |
5 | println!("{}, world!", s1);
| ^^ value borrowed here after move

error: aborting due to previous error

For more information about this error, try `rustc --explain E0382`.
error: could not compile `ownership`

解析:String的所有权从s1转移到s2后,不能再使用s1访问数据。否则违反原则2。

然而,对于下面这段代码,似乎产生了与代码段1矛盾的编译结果。

代码段2:

1
2
3
4
5
fn main() {
let u1:u32 = 20;
let u2 = u1;
println!("{}, world!", u1);
}

如果按代码段1的逻辑,u2=u1时,数字20的所有权传移给u2,u1应该不能再被访问。然而这段代码可以被正确地编译,这是为什么呢?

原因是:针对u32类型的数据,rust赋予其Copy特性(trait)。凡是拥有Copy trait的数据类型,“=”都表示数据的复制而非传有权的转移。因此在u2=u1之后,u2和u1里,都保存有20这一整型数据。

以下数据类型有Copy trait:

  • 所有整型:u32 u64等
  • 布尔型
  • 所有浮点型:f32 f64等
  • 字符型:char
  • 元组(Tuples):如果组成元组的每个成员都有Copy trait,那么此元组也有Copy trait。
错误2

代码段3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn main() {
let x = 5;
print_int(x);
println!("{}", x);
let s = String::from("hello");
print_string(s);
println!("{}", s);
}

fn print_string(some_string: String) {
println!("{}", some_string);
}

fn print_int(some_integer: i32) {
println!("{}", some_integer);
}

编译结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cargo build
Compiling hello v0.1.0 (/home/spencer/share/my_code/rust_prj/hello)
error[E0382]: borrow of moved value: `s`
--> src/main.rs:9:20
|
7 | let s = String::from("hello");
| - move occurs because `s` has type `String`, which does not implement the `Copy` trait
8 | print_string(s);
| - value moved here
9 | println!("{}", s);
| ^ value borrowed here after move

error: aborting due to previous error

解析:函数的传参赋值,视作与“=”有相同的作用。即:变量s被传入print_string函数后,它的所有仅传移给some_string,又因为原则三:“当所有者离开其作用域后,它所拥有的数据会被释放。”some_string所拥有的字符串数据,在离开print_string之后,即被释放。因此第9行的println!("{}", s);s的访问编译失败。
而对整型数据的传参,则不存在类似的问题。因为整型数据有Copy trait,传参意味着数据的复制,而不是所有权的转移。因此第4行println!("{}", x);并没有编译失败。