Rust 所有权系统详解
所有权规则
Rust 的所有权系统是其最独特的特性,它在编译期保证内存安全,无需垃圾回收器。三条核心规则:
- 每个值在 Rust 中都有一个所有者
- 同一时间只能有一个所有者
- 当所有者离开作用域时,值被自动释放
移动语义
当将一个值赋给另一个变量时,所有权会发生转移(move):
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权移动到 s2
// println!("{}", s1); // 编译错误!s1 已失效
这与浅拷贝不同——Rust 会使源变量失效,避免双重释放。
借用与引用
通过引用,可以在不转移所有权的情况下访问值:
fn calculate_length(s: &String) -> usize {
s.len()
} // s 离开作用域,但不释放值
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("{}: {}", s1, len); // s1 仍然有效
可变引用
fn change(s: &mut String) {
s.push_str(" world");
}
let mut s = String::from("hello");
change(&mut s);
限制:同一作用域内,对同一个值只能有一个可变引用,或者多个不可变引用。
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s; // 允许多个不可变引用
let r3 = &mut s; // 编译错误!已有不可变引用
生命周期
生命周期确保引用不会超过被引用值的存活时间:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
'a 是一个生命周期参数,表示返回值的存活时间不会超过 x 和 y 中较短的那个。
生命周期省略规则
编译器在某些情况下可以自动推导生命周期:
- 每个输入引用都有自己的生命周期
- 如果只有一个输入生命周期,它会赋给所有输出引用
- 如果是方法,
&self的生命周期赋给所有输出引用
Drop trait
当值离开作用域时,Drop trait 的 drop 方法被自动调用:
struct Custom {
data: String,
}
impl Drop for Custom {
fn drop(&mut self) {
println!("Dropping: {}", self.data);
}
}
智能指针
Box
用于堆上分配值,适合递归类型和 trait 对象:
enum List {
Cons(i32, Box<List>),
Nil,
}
Rc
引用计数智能指针,允许多个所有者共享只读数据:
use std::rc::Rc;
let a = Rc::new(5);
let b = Rc::clone(&a);
let c = Rc::clone(&a);
RefCell
提供内部可变性,运行时检查借用规则:
use std::cell::RefCell;
let data = RefCell::new(5);
*data.borrow_mut() += 1;
总结
所有权系统让 Rust 在不牺牲性能的前提下保证内存安全。理解所有权、借用和生命周期是掌握 Rust 的关键。