4.2 引用和借用
引用与借用 总结
引用(reference)让你可以在不获取所有权的情况下使用一个值,这种行为称为借用(borrowing)。通过引用,我们不需要为了使用一个值而把所有权传来传去。
1. 不可变引用(&T)
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 传入引用,不转移所有权
println!("The length of '{s1}' is {len}."); // s1 仍然可用
}
fn calculate_length(s: &String) -> usize { // 参数是 &String
s.len()
} // s 离开作用域,但不会释放任何东西,因为它不拥有数据
&s1创建一个指向s1的引用,并不拥有它。- 引用离开作用域时不会调用
drop,值依然归原变量所有。 - 这样就避免了那种“为了看一眼长度而不得不把值传进函数再传出来”的啰嗦写法。
2. 引用默认不可变
fn change(some_string: &String) {
some_string.push_str(", world"); // ❌ 编译错误
}
和变量一样,普通引用(&T)是不可变的,不允许通过它修改原值。
3. 可变引用(&mut T)
如果想要通过引用修改值,需要可变引用:
fn main() {
let mut s = String::from("hello");
change(&mut s); // 传入可变引用
}
fn change(some_string: &mut String) {
some_string.push_str(", world"); // 可以修改
}
- 原变量必须声明为
mut。 - 调用时使用
&mut s,函数签名中用&mut String。
4. 引用的核心规则(防止数据竞争)
Rust 对引用的限制非常严格,目的是在编译期杜绝数据竞争(多个指针同时访问同一数据,至少一个在写,且无同步机制)。
规则一:同一时刻只能有一个可变引用
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // ❌ 编译错误:不能同时存在两个可变引用
可以通过大括号创建新作用域,让两个可变引用先后存在:
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 离开作用域
let r2 = &mut s; // ✅ 可以
规则二:可变引用与不可变引用不能同时存在
let mut s = String::from("hello");
let r1 = &s; // 不可变引用
let r2 = &s; // 允许多个不可变引用
let r3 = &mut s; // ❌ 编译错误
但允许多个不可变引用同时存在,因为它们都只是读取,不会影响彼此。
5. 引用的作用域与 Non-Lexical Lifetimes(NLL)
编译器会精确追踪引用的最后一次使用,而不仅仅看大括号作用域:
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{r1} and {r2}"); // 此后 r1、r2 不再被使用
let r3 = &mut s; // ✅ 可行,因为 r1、r2 的作用域已结束
println!("{r3}");
这种智能的检查让你在实践中少了很多不必要的嵌套作用域。
6. 悬垂引用(Dangling Reference)—— 编译器替你杜绝
fn dangle() -> &String { // 想返回一个引用
let s = String::from("hello");
&s // s 是局部变量
} // s 被释放,返回的引用将指向无效内存——悬垂引用!编译错误。
解决方法:直接返回值的所有权。
fn no_dangle() -> String {
let s = String::from("hello");
s // 所有权移出,安全
}
Rust 编译器确保引用必须总是有效的,绝不允许悬垂指针的存在。
7. 引用规则总结
- 在任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用,两者不能共存。
- 引用必须总是有效的(不会比被引用的值活得更久)。
这些规则在编译期执行,没有运行时开销,却从根本上消灭了数据竞争和悬垂指针问题。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 emoeem!
评论




