引用与借用 总结

引用(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. 引用规则总结

  • 在任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用,两者不能共存。
  • 引用必须总是有效的(不会比被引用的值活得更久)。

这些规则在编译期执行,没有运行时开销,却从根本上消灭了数据竞争和悬垂指针问题。