rust的所有权以及所有权转移机制
Rust 所有权与所有权转移机制拆解
一句话本质
Rust 的所有权是一套编译期内存管理规则:每个值都有一个“所有者”,所有者负责在离开作用域时释放这个值。所谓“所有权转移 / Move”,就是把这个释放责任从旧所有者转交给新所有者,旧所有者随后失效。
一、三条核心规则
-
每个值都有且只有一个所有者
基础模型下,一个值同一时间只能被一个变量、字段或集合元素拥有。
例外:Rc<T>、Arc<T>是显式共享所有权,通过引用计数管理,但每个句柄仍然是强引用所有者之一。 -
同一时间只能有一个所有者
如果发生赋值、传参、返回等操作,所有权可能被移动,旧所有者不能再使用该值。 -
所有者离开作用域,值被丢弃
如果值实现了Drop,会调用drop;没有实现Drop的类型也会释放其拥有的资源。移动后,旧所有者不再负责释放。
二、适用对象:所有值,但分 Copy / 非 Copy
所有 Rust 值都受所有权系统管理,但表现不同:
| 类型 | 赋值 / 传参行为 | 示例 |
|---|---|---|
Copy 类型 |
隐式按位复制,源变量仍可用 | i32、bool、char、&T、[i32; N]、(i32, bool) |
非 Copy 类型 |
移动,源变量失效 | String、Vec<T>、Box<T>、File、&mut T、实现 Drop 的类型 |
| 自定义类型 | 所有字段都 Copy 且未实现 Drop,可 derive(Copy, Clone) |
struct Point { x: i32, y: i32 } |
关键点:
&T是Copy,因为共享引用可以复制。&mut T不是Copy,因为可变引用必须独占。- 实现
Drop的类型不能实现Copy。 Option<i32>是Copy,Option<String>不是。- 数组、元组是否
Copy取决于元素是否全部Copy。
三、所有权转移发生在哪些场景?
Move 常见于:
-
变量赋值
let s1 = String::from("hello"); let s2 = s1; // s1 被移动,s1 失效 -
函数传参
fn take(s: String) {} let s = String::from("a"); take(s); // s 被移动进函数 -
函数返回
fn give() -> String { String::from("x") // 所有权返回给调用者 } -
结构体 / 元组 / 枚举解构或字段赋值
struct P { name: String, age: u8 } let p = P { name: String::from("A"), age: 20 }; let name = p.name; // p.name 被移动 println!("{}", p.age); // 部分移动后,未移动字段仍可用 -
闭包
move捕获let s = String::from("a"); let c = move || println!("{}", s); // s 所有权进入闭包 -
迭代器消费
let v = vec![String::from("a")]; for s in v { // v 被移动,元素所有权逐个转移 println!("{}", s); } -
集合操作
Vec::pop、Vec::remove、Vec::into_iter等会转移元素所有权。
注意:不能直接let x = v[0];移动出Vec元素,因为Vec需要完整释放;可用remove、swap_remove、into_iter等。 -
线程与智能指针
thread::spawn(move || ...)需要把所有权移入新线程。
Box<T>、Rc<T>、Arc<T>等句柄本身移动时,转移的是句柄所有权。
四、Move 的内部机制
Rust 的 Move 不是运行时“搬走堆数据”,而是:
- 编译期:借用检查器跟踪每个变量、字段是否已初始化、是否已被移动。
- 运行时:通常只按位复制栈上的表示,比如
String的指针、长度、容量。 - 源失效:旧所有者被标记为“已移动”,不能再使用、借用或再次移动。
- 释放责任转移:新所有者离开作用域时负责
drop,旧所有者不再drop。
例如:
let s1 = String::from("hello");
let s2 = s1; // 复制 ptr/len/cap,堆上 "hello" 不复制;s1 失效
这避免了双重释放:不会 s1 和 s2 都去释放同一块堆内存。
五、Copy、Clone、Move 的区别
| 操作 | Copy 类型 |
非 Copy 类型 |
|---|---|---|
let b = a; |
复制,a 仍可用 |
移动,a 失效 |
| 函数传参 | 复制 | 移动 |
| 函数返回 | 复制或移动 | 移动 |
a.clone() |
复制 | 显式复制,可能深拷贝 |
实现 Drop |
不允许 | 可以 |
Copy:隐式、廉价、按位复制,不转移所有权。Clone:显式调用,可能深拷贝,可能昂贵。Move:不调用Clone,只是按位复制 + 源失效。
六、借用与所有权的关系
借用不转移所有权:
fn len(s: &String) -> usize { s.len() }
let s = String::from("hello");
let n = len(&s); // 借用,s 仍拥有所有权
println!("{}", s); // ok
借用规则:
- 同一时间,要么有多个
&T,要么有一个&mut T。 - 不能同时存在共享借用和可变借用。
- 借用期间不能移动所有权。
例如:
let s = String::from("a");
let r = &s;
let t = s; // 错误:s 被借用时不能移动
println!("{}", r);
生命周期保证借用不会超过所有者存活时间。
七、部分移动
非 Copy 结构体可以部分移动字段:
struct Person {
name: String,
age: u8,
}
let p = Person { name: String::from("A"), age: 20 };
let name = p.name; // 移动 name 字段
println!("{}", p.age); // ok,age 未移动
// println!("{:?}", p); // 错误:p 已部分移动,整体不可用
注意:如果类型实现了 Drop,不能部分移动字段,因为 Drop::drop 需要完整的 self。
八、特殊所有权模型
Rc<T>/Arc<T>:共享所有权,clone增加引用计数,最后一个强引用释放值。RefCell<T>/Cell<T>:内部可变性,运行时检查借用。MutexGuard<T>:非Copy,移动转移锁守卫,Drop时释放锁。- 闭包:普通闭包按引用或可变引用捕获;
move闭包按值捕获,转移所有权。 - 迭代器:
iter()借用,iter_mut()可变借用,into_iter()转移所有权。
九、总结
Rust 所有权可以概括为:
值有所有者,所有者管释放;移动即转移释放责任,旧所有者失效;Copy 是例外,隐式复制不转移。
它把很多内存安全问题从运行时提前到编译期:
- 避免 use-after-free
- 避免 double free
- 避免数据竞争
- 无需 GC,也能安全管理堆内存
理解所有权后,再去看借用、生命周期、智能指针、闭包和并发,都会顺很多。
浙公网安备 33010602011771号