Rust 没有 GC,又不用你手动 free,全靠编译期那套「所有权」撑着。刚上手那阵我天天跟借用检查器吵架,但把所有权搞明白之后,它的大部分脾气也就理解了。
规则其实就三条:一个值只有一个主人;同一时间只能有一个主人;主人一离开作用域,值就自动清掉。听着简单,写起来照样翻车:
let s1 = String::from("hello");
let s2 = s1; // 所有权搬到 s2 了
// println!("{}", s1); // 编译不过,s1 已经「空」了
第一次撞上这报错的人基本都懵——我就赋了个值,怎么原来那个就不能用了?因为 Rust 里这种类型的 = 是「移动」,不是「拷贝」。
借用:借来用一下,不抢所有权
不想转移所有权,那就借:
fn len(s: &String) -> usize { s.len() } // 借个只读的
let s = String::from("hi");
println!("{}", len(&s)); // s 还是自己的
借用检查器盯得最死的一条:要么一堆只读借用,要么一个可写借用,不能又读又写。这条乍看挺烦,其实就是在编译期帮你把数据竞争堵死了——大家一起读没事,有人要写就不许别人插手。
let mut v = vec![1, 2, 3];
let r1 = &v; // 行
let r2 = &v; // 行,只读随便借
// let m = &mut v; // 不行,前面还有只读借用没还呢
多线程:Arc 配 Mutex
跨线程共享一个可变的东西,标配就是 Arc(共享所有权)加 Mutex(加锁):
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let c = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut n = c.lock().unwrap();
*n += 1;
}));
}
for h in handles { h.join().unwrap(); }
println!("结果 = {}", *counter.lock().unwrap()); // 10
你要是忘了加锁、想直接跨线程改数据——放心,编译器根本不让你过。所谓「无畏并发」就是这意思:能编译过的并发代码,一大类低级错误它已经替你拦掉了。
刚开始所有权是真磨人,逼着你每写一行都想想这数据归谁、能活多久。但熬过那阵就会发现,好多并发的坑你压根没机会踩。
