所有权

Rust 的所有权系统基于三个主要规则:

  • 每个值都有一个所有者(owner)。
  • 每个值在任一时刻只能有一个所有者。
  • 当所有者离开作用域时,值会被自动释放(drop)。

复制和移动

Rust 也拥有和 C++ 类似的复制和移动语义。不同的是,Rust 默认情况下采用移动语义,而不是复制语义。只有实现了 Copy trait 的类型才会被复制。

例如,对于像 i32 这样的基本类型,赋值操作将采用复制语义:

let x = 5;
let y = x; // x 被复制,y 拥有一个独立的副本
println!("x: {}, y: {}", x, y); // x 仍然有效

对于 String 这样的复杂类型,赋值操作将采用移动语义:

let s1 = String::from("hello");
let s2 = s1; // s1 被移动到 s2,s1 不再有效
// println!("s1: {}", s1); // 错误,s1 无效
println!("s2: {}", s2); // 只能使用 s2

Rust 与 C++ 不同之处在于,一旦一个对象被移动,原来的所有者不再持有对该对象的所有权,尝试使用原所有者将导致编译错误。

std::string s1 = "hello";
std::string s2 = std::move(s1); // s1 被移动到 s2
// std::cout << s1; // 未定义行为,s1 可能无效

借用和引用

赋值操作会转移对象的所有权,但有时我们希望多个变量能够访问同一个对象而不转移所有权,典型的场景就是函数参数传递。 Rust 通过借用(borrowing)和引用(references)来实现这一点。

借用和引用是不可分割的两个概念,借用是行为,引用是借用操作完成后得到的一种数据类型。

fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s); // 传递 s 的引用
    println!("The length of '{}' is {}.", s, len); // s 仍然有效
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

在 Rust 中,存在两种类型的引用:不可变引用(&T)和可变引用(&mut T)。

fn main() {
    let mut s = String::from("hello");
    let r1 = &s; // 不可变引用
    let r2 = &s; // 另一个不可变引用
    println!("{} and {}", r1, r2); // 可以同时使用 r1 和 r2
    let r3 = &mut s; // 可变引用
    r3.push_str(", world");
    println!("{}", r3);
    // println!("{}, {}, and {}", r1, r2, r3); // 错误,不能同时使用不可变和可变引用
}

引用的生命周期与一般的变量有所不同:引用的生命周期在其最后一次使用后即结束,而不是在作用域结束时结束。在没有可变引用存在时,可以创建并使用任意数量的不可变引用;但一旦创建了可变引用,且其生命周期没有结束,就不能再创建任何引用(包括可变引用和不可变引用)。这本质上是一个读写者问题:任意时刻要么有任意数量的读者(不可变引用),要么只有一个写者(可变引用)。

fn main() {
    let mut x = 1;
    let b1 = &mut x; // 可变引用
    *b1 += 1; // 通过引用修改 x
    // b1 的生命周期在这里结束
    let b2 = &x; // 不可变引用
    println!("{b2}"); // 可以使用 b2
}

* 操作符用于解引用(dereference)。

fn main() {
    let mut x = 1;
    let b1 = &mut x; // 可变引用
    let b2 = &x; // 错误:不能在可变引用存在(b1 的生命周期仍未结束)时创建不可变引用
    println!("{}, {}", b1, b2);
}

Rust 2018 引入了非词法生命周期(NLL,Non-Lexical Lifetimes),使得引用的生命周期更加灵活。引用的生命周期不再严格受限于其作用域,而是根据实际使用情况进行推断,从而减少了不必要的借用冲突。在更早版本的 Rust 中,引用的生命周期与普通变量一样,随着作用域结束而结束。