logoProsperBao

Rust 引用与借用

2022-09-15 03 · 15min

引用

引用像一个指针,因为它是一个地址,可以由此访问储存于该地址的属于其他变量的数据。与指针不同,引用确保指向某个特定类型的有效值。

与使用 & 引用相反的操作是 解引用,它使用解引用运算符,*

let s1 = String::from("hello");
let len = calculate_length(&s1);

&s1 语法让创建一个 指向s1 的引用,但是并不拥有它。因为并不拥有这个值,所以当引用停止使用时,它所指向的值也不会被丢弃。

同理,函数签名使用 & 来表明参数 s 的类型是一个引用。:

fn calculate_length(s: &String) -> usize { // s 是一个引用字符串
    s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
  // 所以什么也不会发生

变量 s 有效的作用域与函数参数的作用域一样,不过当 s 停止使用时并不丢弃引用指向的数据,因为 s 并没有所有权。

当函数使用引用而不是实际值作为参数,无需返回值来交还所有权,因为就不曾拥有所有权。

借用

& 后面添加 mut 使得引用可变:

fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

可变引用有一个很大的限制:

如果有一个对该变量的可变引用,就不能再创建对该变量的引用。这些尝试创建两个 s 的可变引用的代码会失败

let mut s = String::from("hello");

let r1 = &mut s;
      // ------ 第一个可变以用
let r2 = &mut s;
      // ^^^^^^ 第二个可变引用

println!("{}, {}", r1, r2);

防止同一时间对同一数据进行多个可变引用的限制允许可变性,不过是以一种受限制的方式允许。

因为大部分语言中变量任何时候都是可变的。这个限制的好处是 Rust 可以在编译时就避免数据竞争。

数据竞争类似于竞态条件,它可由这三个行为造成:

  • 两个或更多指针同时访问同一数据。
  • 至少有一个指针被用来写入数据。
  • 没有同步数据访问的机制。

数据竞争会导致未定义行为,难以在运行时追踪,并且难以诊断和修复。

Rust 避免了这种情况的发生,因为 Rust 不会编译存在数据竞争的代码。

let mut s = String::from("hello");

{
    let r1 = &mut s;
} // r1 在这里离开了作用域,所以完全可以创建一个新的引用

let r2 = &mut s;

可以使用大括号来创建一个新的作用域,以允许拥有多个可变引用,只是不能 同时 拥有。

Rust 在同时使用可变与不可变引用时也采用的类似的规则。

let mut s = String::from("hello");

let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 寄了

println!("{}, {}, and {}", r1, r2, r3);

不能在拥有不可变引用的同时拥有可变引用,但是不可引用在之后没有使用则是可以的,这称为 非词法作用域生命周期

let mut s = String::from("hello");

let r1 = &s; // 没问题
let r2 = &s; // 没问题
println!("{} and {}", r1, r2);
// 此位置之后 r1 和 r2 不再使用

let r3 = &mut s; // 没问题
println!("{}", r3);

这是 Rust 编译器在提前指出一个潜在的 bug(在编译时而不是在运行时)并精准显示问题所在。

悬垂引用

在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 悬垂指针,所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。

相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态,当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s // 不可以返回一个指向 s 的引用,因为 s 将离开作用域并被丢弃
    // 改为返回 s
}

引用的规则

  • 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。

  • 引用必须总是有效的。