3.3 引用与借用
听
Rust 的所有权系统虽然保证了内存安全,但若每次使用数据都需要转移所有权,代码将变得非常受限。为了解决这个问题,Rust 引入了引用(reference)和借用(borrowing)机制:允许函数或代码块临时访问某个值,而无需获取其所有权。
什么是引用?
引用类似于指针,它指向某个值所在的内存地址,但不拥有该值。在 Rust 中,使用 & 符号创建引用:
let s = String::from("hello");
let r = &s; // r 是对 s 的引用
这里,r 的类型是 &String,读作“字符串的引用”。因为 r 只是借用了 s,所以当 r 离开作用域时,s 不会被释放,其所有权仍属于原变量。
不可变借用与可变借用
Rust 区分两种引用:
- 不可变引用(
&T):只能读取数据,不能修改。 - 可变引用(
&mut T):可以读取和修改数据。
例如:
let mut s = String::from("hello");
let r1 = &s; // 不可变借用
let r2 = &s; // 可以有多个不可变借用
println!("{} and {}", r1, r2);
let r3 = &mut s; // 错误!此时已有不可变引用存在
Rust 对引用施加了严格的规则:
- 在任意给定作用域内,你可以有:
- 任意数量的不可变引用,或
- 恰好一个可变引用。
- 引用必须总是有效的(即不能是悬垂引用)。
这些规则确保了数据竞争(data race)在编译期就被阻止——数据竞争是指多个线程或代码路径同时读写同一数据且至少有一个是写操作,而没有同步机制。
借用作为函数参数
函数可以通过接受引用来避免获取所有权。这是 Rust 中最常见的方式:
fn print_length(s: &String) {
println!("Length: {}", s.len());
}
let s = String::from("hello");
print_length(&s); // 传入引用,s 的所有权未转移
println!("{}", s); // 仍然可以使用 s
如果需要在函数中修改数据,则使用可变引用:
fn push_exclamation(s: &mut String) {
s.push_str("!");
}
let mut s = String::from("hello");
push_exclamation(&mut s);
println!("{}", s); // 输出 "hello!"
注意:调用者必须提供可变变量的可变引用,即变量本身需声明为 mut。
借用的生命周期
引用的作用域不能超过其所引用值的生命周期。编译器会自动检查这一点,防止出现悬垂引用(将在下一节详述)。
例如,以下代码无法编译:
let r;
{
let x = 5;
r = &x; // 错误:x 在此作用域结束时被释放
}
println!("{}", r); // r 指向已释放的内存
小结
引用与借用机制让 Rust 在不牺牲安全性的前提下,实现了灵活的数据共享。通过限制不可变与可变引用的共存方式,Rust 在编译期消除了数据竞争的可能性。理解如何正确使用 &T 和 &mut T,是编写高效、安全 Rust 代码的关键一步。
#Rust 入门教程
分享于 1 周前
上一篇:3.2 移动语义与复制语义
下一篇:3.4 悬垂引用及其避免方法