Rust 中的所有权

Rust 语言的内存管理不同于其他语言常见的GC和手动内存释放, 是通过所有权这一机制来实现的.

所有权机制有如下特点

  • Rust中的变量在离开作用域之后就会直接被丢弃.
  • 一个变量只有一个所有者

一个变量只拥有一个所有者, 也就是说, 当这个位置上的指针指向的值有新的指针指向之后, 这个指针将会直接被丢弃.

fn main() {
    let s1 = String::from("value");
    let s2 = s1;
    println!("{s1}");
}

上述代码在编译时将会遇到如下报错:

error[E0382]: borrow of moved value: `s1`
  --> src/main.rs:19:15
   |
17 |     let s1 = String::from("value");
   |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
18 |     let s2 = s1;
   |              -- value moved here
19 |     println!("{s1}");
   |               ^^^^ value borrowed here after move
   |
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
   |
18 |     let s2 = s1.clone();
   |                ++++++++

For more information about this error, try `rustc --explain E0382`.
error: could not compile `test1` (bin "test1") due to 1 previous error

简单的let s2 = s1操作只是将s1的指针交由s2进行保管, 如果需要进行深拷贝, 则要使用let s2 = s1.clone()


那么为什么这段代码却不会报错呢

fn main() {
    let s1 = "str";
    let s2 = s1;
    println!("{s1}");
}

首先, 分配给程序的内存分为两个不同的结构.

是一种LIFO(后进先出)结构, 每次都只能操作栈顶的元素进行push(压栈)或是pop(弹栈), 正因为结构简单, 栈上的操作相当迅速, 对于"str"这种简单的常量字符串直接复制一份也花不了太长时间.

String::from("value")则是可变的String类型, 这种类型可能会随时变化内存的占用大小, 需要存储在堆区(因为栈区的操作限制, 不能动态分配内存). 在堆区操作相对较慢, 将所有数据复制出一份新的(深拷贝)将会浪费大量时间.

拷贝指针的方式固然很快, 但是又出现了一个问题, s1s2指向同一片内存, 在释放内存的时候就会遇到二次释放的问题.

Rust的所有权问题就可以很好的处理这个问题. 因为这个特殊的机制, let a = b这种操作也被称为变量绑定.

既然这样, 如果我还是需要两份一样的字符串该怎么做呢?

可以新建一个指针引用, 这个指针指向s1, 再通过s1的指针指向实际的String(这种操作称为借用), 就像下面这样

fn main() {
    let s1 = String::from("str");
    let s2 = &s1;
    println!("{s1}");
}

如果需要修改指针指向的值, 需要给s1加上可变声明

fn main() {
    let mut s1 = String::from("str");
    let s2 = &mut s1;
    change(s2);
}

fn change(s: &mut str) -> usize {
    s.len()
}

虽然使用可变引用的方式很方便, 但是可变引用在同一作用域对单个数据智能存在一个. 所以下面的代码就会引发报错.

fn main() {
    let mut s1 = String::from("str");

    let s2 = &mut s1;
    let s3 = &mut s1;

    println!("{s2}{s3}");
}

报错如下

error[E0499]: cannot borrow `s1` as mutable more than once at a time

在同一个作用域下使用多个可变引用可能会有数据污染的问题, 在一个可变引用中修改了值后另一个可变引用并不知道...

除此之外, Rust总是要求你使用的引用是有效的, 如果存在 c 语言下类似的悬空指针的问题将无法通过编译.

fn main() {
    let s1 = new_s();

    println!("{s1}");
}

fn new_s() -> &String {
    let s = String::from("value");
    &s
}

上面的代码会提示this function's return type contains a borrowed value, but there is no value for it to be borrowed from.

一个简单的解决办法就是直接返回一个String

(就算你声明为静态也是不允许的, 就像下面这样)

fn main() {
    let s1 = new_s();

    println!("{s1}");
}

fn new_s() -> &String {
    static s = String::from("value");
    &s
}

报错:

error[E0015]: cannot call non-const associated function `<String as From<&str>>::from` in statics

因为Rust需要一个 static 变量在编译时就能确定其值.

Rust的内存安全简直丧心病狂...


参考:
Rust 程序设计语言 简体中文版
Rust 语言圣经(Rust Course)