Rust 所有权实战:无惧构建内存安全系统
Rust 的所有权系统是该语言的标志性特性——一种编译时机制,消除了包括空指针解引用、悬垂指针、数据竞争和释放后使用在内的整类错误。经过多年的生产使用,已经涌现出一些模式,使得与借用检查器协作变得自然而非令人沮丧。
所有权的三条规则
Rust 的所有权系统建立在三条基本规则之上:
- Rust 中的每个值都有一个唯一的所有者。
- 同一时间只能有一个所有者。
- 当所有者离开作用域时,该值将被丢弃。
fn main() {
let s1 = String::from("hello"); // s1 拥有该 String
let s2 = s1; // 所有权 移动 到 s2
// println!("{}", s1); // 错误:s1 已被移动
println!("{}", s2); // 正确
} // s2 离开作用域,String 被丢弃

借用和引用
借用允许你在不获取所有权的情况下使用值:
fn calculate_length(s: &String) -> usize {
s.len()
}
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("Length of '{}' is {}", s1, len); // s1 仍然有效
}
可变引用允许修改,但有约束:
fn change(s: &mut String) {
s.push_str(", world");
}
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s); // "hello, world"
}
借用检查器在编译时强制执行这些规则:
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 正确:不可变借用
let r2 = &s; // 正确:允许多个不可变借用
// let r3 = &mut s; // 错误:存在不可变借用时
println!("{} and {}", r1, r2); // r1, r2 最后使用处
let r3 = &mut s; // 现在正确
println!("{}", r3);
}
理解生命周期
生命周期确保引用不会超过它们所指向的数据的存活时间:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("long string");
let result;
{
let s2 = String::from("xyz");
result = longest(s1.as_str(), s2.as_str());
println!("Longest: {}", result);
}
}
结构体中的生命周期标注:
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.part
}
}
选择 Box、Rc 和 Arc
Box:堆上的单一所有权
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
fn main() {
let list = List::Cons(1,
Box::new(List::Cons(2,
Box::new(List::Cons(3,
Box::new(List::Nil))))));
println!("{:?}", list);
}

Rc:多重所有权(单线程)
use std::rc::Rc;
fn main() {
let a = Rc::new(vec![1, 2, 3]);
let b = Rc::clone(&a);
let c = Rc::clone(&a);
println!("Reference count: {}", Rc::strong_count(&a)); // 3
drop(c);
println!("After drop: {}", Rc::strong_count(&a)); // 2
}
Arc:跨线程的多重所有权
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap()); // 10
}
内部可变性:RefCell 和 Mutex
有时你需要在借用检查器不允许的情况下获得可变性:
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
value: i32,
children: RefCell<Vec<Node>>,
}
impl Node {
fn new(value: i32) -> Self {
Node { value, children: RefCell::new(vec![]) }
}
fn add_child(&self, child: Node) {
self.children.borrow_mut().push(child);
}
}
内存安全并发
Rust 的所有权系统在编译时使数据竞争变得不可能:
use std::sync::{Arc, RwLock};
use std::thread;
use std::collections::HashMap;
type SharedCache = Arc<RwLock<HashMap<String, String>>>;
async fn cache_put(cache: SharedCache, key: String, value: String) {
let mut w = cache.write().unwrap();
w.insert(key, value);
}
async fn cache_get(cache: SharedCache, key: &str) -> Option<String> {
let r = cache.read().unwrap();
r.get(key).cloned()
}

常见陷阱及如何避免
避免过度使用 Clone
// 糟糕:不必要地克隆
fn process_names_bad(names: Vec<String>) -> Vec<String> {
names.iter()
.map(|n| n.clone().to_uppercase())
.collect()
}
// 良好:使用引用
fn process_names(names: &[String]) -> Vec<String> {
names.iter()
.map(|n| n.to_uppercase())
.collect()
}
丢弃顺序很重要
use std::sync::Mutex;
struct Foo { mutex: Mutex<i32> }
fn safe() {
let foo = Foo { mutex: Mutex::new(0) };
{
let guard = foo.mutex.lock().unwrap();
// 使用 guard
} // guard 在此处丢弃,互斥锁解锁
drop(foo); // 现在可以安全丢弃
}
实用模式:带所有权的构建器
#[derive(Debug)]
struct Config {
host: String,
port: u16,
max_connections: usize,
}
struct ConfigBuilder {
host: String,
port: u16,
max_connections: usize,
}
impl ConfigBuilder {
fn new() -> Self {
ConfigBuilder {
host: "localhost".to_string(),
port: 8080,
max_connections: 100,
}
}
fn host(mut self, host: impl Into<String>) -> Self {
self.host = host.into();
self
}
fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
fn build(self) -> Config {
Config {
host: self.host,
port: self.port,
max_connections: self.max_connections,
}
}
}
fn main() {
let config = ConfigBuilder::new()
.host("production.example.com")
.port(443)
.build();
println!("{:?}", config);
}
结论
Rust 的所有权系统不是限制——而是一种超能力。通过在类型系统中编码内存安全规则,Rust 消除了困扰 C 和 C++ 程序的整类错误。初期的学习曲线会在生产可靠性上得到回报。从简单的拥有类型开始,在性能关键时使用引用,跨线程共享时使用 Arc/Mutex。通过练习,借用检查器将成为值得信赖的协作者而非对手。