Skip to content

Rust - 借用

在所有权(Ownership)一章中,我们了解到当一个值被分配给另一个变量或传递给一个函数时,它的所有权会被 移动(moved)。移动之后,原所有者不能再使用该值。这确保了内存安全,但也可能带来不便。不断地来回传递所有权会很麻烦。

这就是借用(borrowing)的作用。借用允许你创建一个值的引用(reference),从而在不取得所有权的情况下访问它。

fn calculate_length(s: String) -> (String, usize) {
let length = s.len();
(s, length) // Return ownership of the String and the length
}
fn main() {
let s1 = String::from("hello");
// We pass s1, and also get it back.
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}.", s2, len);
}

这段代码可以工作,但它很笨拙。我们不得不从函数中返回 String,以便能够继续使用它。这正是借用的完美用例。

我们可以传递值的引用(&String),而不是直接传递值本身(String)。引用就像一个指针,它保证指向特定类型的有效值。& 符号是引用运算符。

// The function now takes a reference to a String.
fn calculate_length(s: &String) -> usize {
s.len()
} // `s` goes out of scope here, but because it does not own what it refers to,
// the String is not dropped.
fn main() {
let s1 = String::from("hello");
// We pass a reference to s1. Ownership is not moved.
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
// s1 is still valid here!
}
The length of 'hello' is 5.

正如变量默认是不可变的,引用也是如此。有两种类型的引用:

  • 不可变引用(Immutable Reference)(&T):允许你读取数据但不能修改它。
  • 可变引用(Mutable Reference)(&mut T):允许你同时读取和修改数据。

要创建可变引用,你使用 &mut。原始变量也必须声明为 mut。* 运算符用于**解引用(dereference)**引用,允许你访问它指向的值。

fn add_suffix(s: &mut String) {
s.push_str(" World"); // Methods can be called directly on the reference
}
fn increment(num: &mut i32) {
*num += 1; // For primitive types, we must dereference with *
}
fn main() {
let mut my_string = String::from("Hello");
println!("Before: {}", my_string);
add_suffix(&mut my_string);
println!("After: {}", my_string);
let mut count = 5;
println!("\nBefore: {}", count);
increment(&mut count);
println!("After: {}", count);
}
Before: Hello
After: Hello World
Before: 5
After: 6

Rust 编译器强制执行一套关键规则,以在编译时防止数据竞争(data races)和其他并发错误。这些规则适用于给定作用域内的引用:

  1. 规则 1:你可以拥有一个可变引用,或者任意数量的不可变引用。
  2. 规则 2:引用必须始终有效(即,它们不能是悬垂引用(dangling references),指向已释放的内存)。

这意味着当不可变引用存在时,你不能拥有一个可变引用。所有不可变读者都必须消失,然后才能获得一个可变写入器。

以下代码将无法编译,因为它违反了规则 1。

// This code will fail to compile!
fn main() {
let mut s = String::from("hello");
let r1 = &s; // an immutable borrow starts here
let r2 = &s; // another immutable borrow is fine
// COMPILE ERROR: cannot borrow `s` as mutable because it is also borrowed as immutable
let r3 = &mut s;
// The compiler knows that r1 and r2 could be used after the mutable borrow, which is unsafe.
println!("Immutable borrows: {}, {}", r1, r2);
}

编译器的错误消息在这里非常有帮助。它防止了数据在被读取时意外更改的情况,这很不安全。

  • 优先选择不可变借用:当你只需要读取数据时,使用 &T。这更安全,并允许更灵活的代码。
  • 保持可变借用简短:可变借用的作用域应尽可能小,以允许代码的其他部分访问数据。
  • 对集合使用切片:编写操作数组或向量的函数时,接受切片(&[T] 或 &mut [T]),而不是直接引用集合(&Vec<T>)。这使你的函数更通用和可重用。