Skip to content

11.1 什么是所有权

拿 C 语言的代码来打个比方。我们可能会在堆上创建一个对象,然后使用一个指针来管理这个对象:

rust
Foo *p = make_object("args");

接下来,我们可能需要使用这个对象:

rust
use_object(p);

然而,这段代码之后,谁能猜得到,指针 p 指向的对象究竟发生了什么?它是否被修改过了?它还存在吗,是否已经被释放?是否有另外一个指针现在也同时指向这个对象? 我们还能继续读取、修改或者释放这个对象吗?实际上,除了去了解use_object的内部实现之外,我们没办法回答以上问题。

对此,C++ 进行了一个改进,即通过“智能指针”来描述“所有权”(Ownership)概念。这在一定程度上减少了内存使用 bug,实现了“半自动化”的内存管理。 而 Rust 在此基础上更进一步,将“所有权”的理念直接融入到了语言之中。

Rust 有以下 3 条所有权规则:

  • 每个值在 Rust 中都有一个变量来管理它,这个变量就是这个值、这块内存的所有者;

  • 每个值在一个时间点上只有一个所有者;

  • 当变量所在的作用域结束的时候,变量以及它代表的值将会被销毁。

规则 1 给出了所有者owner的概念,规则 2 对应所有权的转移 Move 语义,规则 3 对应内存释放 Drop。 Rust通过单一所有权,将堆上内存管理与分配在栈上的所有者变量生命周期绑定,所有者离开作用域后堆内存也自动释放。

我们再拿前面已经讲过的字符串 String 类型来举例:

rust
fn main() {
    let mut s = String::from("hello");
    s.push_str(" world");
    println!("{}", s);
}

当我们声明一个变量s,并用 String 类型对它进行初始化的时候,这个变量s就成了这个字符串的“所有者”。 如果我们希望修改这个变量,可以使用mut修饰s,然后调用 String 类型的成员方法来实现。 当main函数结束的时候,s将会被析构,它管理的内存(不论是堆上的,还是栈上的)则会被释放。

我们一般把变量从出生到死亡的整个阶段,叫作一个变量的“生命周期”。比如这个例子中的局部变量s,它的生命周期就是从let语句开始,到main函数结束。

在上述示例的基础上,若做一点修改:

rust
fn main() {
    let s = String::from("hello");
    let s1 = s;
    println!("{}", s);
}

编译,可见:

error[E0382]: use of moved value: `s`
 --> test.rs:5:20
  |
4 |     let s1 = s;
  |         -- value moved here
5 |     println!("{}", s);
  |                  ^ value used here after move
  |
  = note: move occurs because `s` has type `std::string::String`, which does not implement the `Copy` trait

这里出现了编译错误。编译器显示,在let s1 = s;语句中,原本由s拥有的字符串已经转移给了s1这个变量。所以,后面继续使用s是不对的。

也就是前面所说的每个值只有一个所有者。变量s的生命周期从声明开始,到 move 给s1就结束了。变量s1的生命周期则是从它声明开始,到函数结束。 而字符串本身,由String::from函数创建出来,到函数结束的时候就会销毁。中间所有权的转换,并不会将这个字符串本身重新销毁再创建。 在任意时刻,这个字符串只有一个所有者,要么是s,要么是s1

请注意,Rust 的赋值和 C++ 的赋值有重大区别。如果我们用 C++ 来实现上面这个例子的话,程序如下:

rust
#include <iostream>

using namespace std;

int main() {
    string s("hello");
    string s1 = s;
    cout << s << endl;
    cout << s1 << endl;
    return 0;
}

在用变量s初始化s1的时候,并不会造成s的生命周期结束。这里只会调用 string 类型的复制构造函数复制出一个新的字符串,于是在后面s1s都是合法变量。 在 Rust 中,我们要模拟这一行为,需要手动调用clone()方法来完成:

rust
fn main() {
    let s = String::from("hello");
    let s1 = s.clone();
    println!("{} {}", s, s1);
}

在 Rust 里面,不可以做“赋值运算符重载”,若需要“深复制”,必须手工调用clone方法。 这个clone方法来自于std::clone::Clone这个 trait。clone方法里面的行为是可以自定义的。

Released under the MIT License