很多人把 Rust 的难度理解成语法。真正难的是:值只有一个主人。 其余的 &&mut、生命周期,都是在给这句话加注解。

Rustsrc/main.rs
1fn take(name: String) {2    println!("owned {name}");3}4 5fn main() {6    let title = String::from("折页");7    take(title);8    // println!("{title}"); // value borrowed here after move9}

take 吃掉了 title。再打印一次,编译器不是矫情,是在阻止你读一块已经不属于你的栈槽。

借用,而不是复制

多数时候你并不想转移所有权,只想临时看看。那就借。

Rustsrc/borrow.rs
1fn longest<'a>(left: &'a str, right: &'a str) -> &'a str {2    if left.len() >= right.len() { left } else { right }3}4 5fn pick<'a>(hay: &'a str, needle: &str) -> Option<&'a str> {6    hay.find(needle).map(|index| &hay[index..index + needle.len()])7}

'a 读作:返回值不能比两个输入里较短的那个活得更久。它不是魔法标记,是在函数签名里把「别把局部变量的地址带出栈帧」写成类型。

可变借用更严:同一时刻,&mut T 只能有一个。这不是风格问题,是为了让数据竞争在编译期破产。

Rustsrc/mut.rs
1fn main() {2    let mut buf = String::from("fn main");3    let view = &buf;4    // buf.push('!'); // cannot borrow as mutable because it is also borrowed as immutable5    println!("{view}");6}

什么时候该 clone

clone 能让程序通过编译,也能把热路径变成复印件工厂。经验法则很短:

  1. 跨线程、要拥有数据 → clone / Arc
  2. 函数只读 → &T
  3. 函数要改、且调用方不再用 → 直接拿走
  4. 循环里 clone 之前,先问能不能改签名
借用检查器查的不是你聪不聪明,是这块字节之后还会不会有人读。

把所有权想成账本,Rust 就没那么神秘。账本记不清的语言,把同样的问题推迟到了 core dump。Go 不靠类型拦住数据竞争,它把所有权交给 channel,见 select 与超时。类型层把非法状态删掉的做法,和 判别联合 是同一类约束。编译期能确定的事,Zig 会直接算掉,见 zig-comptime