4.3 可变借用与不可变借用
在 Rust 中,借用(Borrowing)是所有权系统的一个核心机制,它允许我们临时访问一个值而不转移其所有权。借用分为两种:不可变借用(Immutable Borrow)和可变借用(Mutable Borrow)。理解这两种借用的区别以及它们之间的交互规则,是安全编写 Rust 代码的关键。
4.3.1 不可变借用
不可变借用允许你读取一个值,但不能修改它。你可以通过 & 符号创建一个不可变引用,从而实现对值的不可变借用。
示例:
fn main() {
let s = String::from("Hello");
// 创建不可变引用
let len = calculate_length(&s);
println!("The length of '{}' is {}.", s, len);
}
fn calculate_length(s: &String) -> usize {
// s 是一个指向 String 的引用,但无法修改它
s.len()
} // 这里 s 离开作用域,但它并不拥有所有权,所以不会发生 drop 操作
在这个例子中,calculate_length 函数通过 &s 借用了 s 的所有权,但它只能读取 s 的内容,不能修改。这保证了在借用期间,原始变量 s 仍然有效且未被意外修改。
4.3.2 可变借用
可变借用允许你读取并修改一个值。你可以通过 &mut 符号创建一个可变引用,从而实现对值的可变借用。
示例:
fn main() {
let mut s = String::from("Hello");
// 创建可变引用
change(&mut s);
println!("{}", s);
}
fn change(s: &mut String) {
// 通过可变引用修改字符串
s.push_str(", world!");
}
在这个例子中,change 函数通过 &mut s 借用了 s 的可变引用,并成功地向字符串追加了内容。注意,原始变量 s 必须声明为 mut,才能创建可变引用。
4.3.3 借用的核心规则
Rust 的借用规则旨在防止数据竞争(Data Races)和悬垂引用(Dangling References)。以下是两条最核心的规则:
- 在同一时间,你只能拥有一个可变引用,或者多个不可变引用。
- 引用必须始终有效。
规则一详解:
多个不可变引用是允许的:因为多个读者同时读取数据是安全的,不会产生冲突。
let s = String::from("Hello"); let r1 = &s; // 不可变借用 1 let r2 = &s; // 不可变借用 2 println!("{} and {}", r1, r2); // 可以同时使用一个可变引用是允许的:因为只有一个写者,可以确保数据一致性。
let mut s = String::from("Hello"); let r1 = &mut s; // 可变借用 // let r2 = &mut s; // 错误!不能同时有两个可变引用不可变引用与可变引用不能共存:因为如果存在一个读者,写者修改数据可能导致读者读到不一致的状态。
let mut s = String::from("Hello"); let r1 = &s; // 不可变借用 let r2 = &mut s; // 错误!不能同时存在不可变引用和可变引用 println!("{}", r1);编译器会报错,因为它检测到
r1(不可变引用)和r2(可变引用)同时存在。
规则二详解:
- 引用不能比它引用的值存活得更久。这主要涉及到生命周期(Lifetimes)的概念,但一个简单的例子可以说明:这个函数试图返回一个指向局部变量
fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { let s = String::from("hello"); &s // 返回指向局部变量 s 的引用 } // 这里 s 离开作用域并被销毁,其内存被释放s的引用,但s在函数返回时就被销毁了,导致reference_to_nothing指向无效内存。Rust 编译器会拒绝这种代码,防止悬垂引用的产生。
4.3.4 借用的作用域
借用的作用域从它被创建的地方开始,一直持续到它最后一次被使用的地方。这允许我们在借用结束后,重新获得对原变量的完全控制。
示例:
fn main() {
let mut s = String::from("Hello");
let r1 = &s; // 不可变借用开始
let r2 = &s; // 另一个不可变借用开始
println!("{} and {}", r1, r2); // 最后一次使用 r1 和 r2
// 这里 r1 和 r2 的作用域结束
let r3 = &mut s; // 现在可以创建可变引用了
println!("{}", r3);
}
在这个例子中,不可变引用 r1 和 r2 在 println! 之后就不再被使用,因此它们的作用域结束。之后,我们就可以安全地创建一个可变引用 r3。
4.3.5 总结
- 不可变借用 (
&T):允许多个读者同时读取数据,但不能修改。 - 可变借用 (
&mut T):允许唯一的写者读取和修改数据。 - 核心规则:在同一时间,要么有多个不可变引用,要么有一个可变引用。引用必须始终有效。
- 作用域:借用的作用域持续到其最后一次使用,这允许在借用结束后进行其他操作。
掌握可变与不可变借用的规则,是驾驭 Rust 所有权系统的关键一步。它帮助你在编译时就消除数据竞争,编写出既高效又安全的代码。
