移动特性
C++11 会通过移动语义来消除内存拷贝成本,而 rust 将这种特性发挥到极致,推出了所有权机制。
看一段代码:
1 2
| let s1 = String::from("hello"); let s2 = s1;
|
当 s2 绑定 s1 的资源的时候,就会将 s1 的资源转移到 s2 中,这是因为一份资源只能有一个拥有者。
换句话说,s1 资源转移到 s2 中,不仅仅发生了浅拷贝,而且 s1 变量也无效了。这个和 C++ 还不一样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <iostream> #include <utility>
class ResourceHolder { public: int* data;
ResourceHolder(int value) : data(new int(value)) {}
ResourceHolder(ResourceHolder&& other) noexcept : data(other.data) { other.data = nullptr; }
~ResourceHolder() { delete data; } };
int main() { ResourceHolder obj1(42); ResourceHolder obj2 = std::move(obj1);
if (obj1.data == nullptr) { std::cout << "obj1.data 已被置空" << std::endl; }
return 0; }
|
C++ 的对象被移动之后,原有对象并没有因此失效,只是资源不可访问。而 Rust 对象是彻底的实效。
克隆(深拷贝)
Rust 永远没有所谓的深拷贝,只能通过 clone 来实现资源在堆上的复制:
1 2 3 4
| let s1 = String::from("hello"); let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
|
浅拷贝
前面说的资源的转移,就附带了浅拷贝,浅拷贝发生在栈上。带有 Copy 特征的对象都可以在栈上浅拷贝:
- 所有整数类型,比如 u32
- 布尔类型,bool,它的值是 true 和 false
- 所有浮点数类型,比如 f64
- 字符类型,char
- 元组,当且仅当其包含的类型也都是 Copy 的时候。比如,(i32, i32) 是 Copy 的,但 (i32, String) 就不是
- 不可变引用 &T ,例如转移所有权中的最后一个例子,但是注意:可变引用 &mut T 是不可以 Copy的
函数
函数是一个表达式,它有返回值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| fn main() { let s = String::from("hello");
takes_ownership(s);
let x = 5;
makes_copy(x);
}
fn takes_ownership(some_string: String) { println!("{}", some_string); }
fn makes_copy(some_integer: i32) { println!("{}", some_integer); }
|
同样,函数的返回值也有所有权:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| fn main() { let s1 = gives_ownership();
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2); }
fn gives_ownership() -> String {
let some_string = String::from("hello");
some_string }
fn takes_and_gives_back(a_string: String) -> String {
a_string }
|
悬垂引用(Dangling References)
考虑一个问题,如果引用 s 的资源没了,怎么办?先来看看这个简单的 C 语言例子:
1 2 3 4 5 6 7
| int* foo() { int a; a = 100; char *c = "xyz"; return &a; }
|
这个函数返回后,a 被释放了,因此返回的值带来不确定性。从根本上讲,虽然不会报错,但是这是典型的非法例子。
再考虑类似的情景:
1 2 3 4 5 6 7 8 9
| fn main() { let reference_to_nothing = dangle(); }
fn dangle() -> &String { let s = String::from("hello");
&s }
|
当 s 离开作用域之后资源被释放了,那么 s 的引用自然就没有意义了。Rust 很严格,编译直接报错,不像 C 那样危险。