借东西:引用与借用
原理
引用(Reference)在底层是裸指针(pointer)加编译期约束。&T 编译后就是一个 *const T,&mut T 是 *mut T,零运行时开销。但借用检查器(borrow checker)为它们加了严格的别名规则。
核心规则来自编译器 NLL(Non-Lexical Lifetimes)分析:
- 任意时刻,要么一个可变引用(
&mut),要么多个不可变引用(&),不能共存 - 引用的生命周期不能超过被引用数据的生命周期
这条规则的根本目的是防止数据竞争和悬垂指针。LLVM 的 noalias 属性由此获得保证,启用大量优化。违反规则导致编译错误,而非运行时崩溃。
&str 是宽指针(2*usize: ptr + len),指向 UTF-8 字节序列。&String 通过 Deref trait 自动转型为 &str。两者均不拥有数据。
语法
创建引用
let s = String::from("hello");
let r = &s; // 不可变引用
let mut m = s;
let rm = &mut m; // 可变引用解引用
let x = 5;
let r = &x;
assert_eq!(5, *r); // * 显式解引用
println!("{}", r); // 打印自动解引用借用规则
| 规则 | 含义 |
|---|---|
多个 &T | 可同时存在(共享借用) |
单一 &mut T | 唯一可变引用(独占借用) |
&T 和 &mut T | 不能共存 |
let mut s = String::from("hi");
let r1 = &s;
let r2 = &s; // 合法:多个不可变引用
// let r3 = &mut s; // 错误:已有不可变引用存在
println!("{} {}", r1, r2);
let r3 = &mut s; // 合法:r1 和 r2 已不再使用函数参数中的引用
fn len(s: &String) -> usize { s.len() } // 只读借用
fn append(s: &mut String) { s.push_str("!"); } // 可变借用
let mut s = String::from("hello");
len(&s);
append(&mut s);悬垂引用(编译器阻止)
// fn dangle() -> &String {
// let s = String::from("hi");
// &s // 错误:s 被释放后引用悬空
// }String vs &str
| 类型 | 所有权 | 可变 | 大小 |
|---|---|---|---|
String | 拥有数据 | 是 | 24B(ptr+len+cap) |
&str | 借用 | 否 | 16B(ptr+len) |
&String | 借用 | 否 | 8B(单指针)→ Deref 为 &str |
实践
力扣问题
力扣: 力扣字符串处理题 — 涉及字符串切片
fn main() {
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let mut s = String::from(input.trim());
// 操作 &s[..] 和 &mut s
}AI 自检
&mut为何不能与&共存?从 CPU 缓存一致性和 LLVMnoalias优化角度解释。&str与&String在栈上的字节数分别是多少?为什么后者可通过 Deref 自动转换?