AIGC标识 rust的所有权以及所有权转移机制

Rust 所有权与所有权转移机制拆解

一句话本质

Rust 的所有权是一套编译期内存管理规则:每个值都有一个“所有者”,所有者负责在离开作用域时释放这个值。所谓“所有权转移 / Move”,就是把这个释放责任从旧所有者转交给新所有者,旧所有者随后失效。


一、三条核心规则

  1. 每个值都有且只有一个所有者
    基础模型下,一个值同一时间只能被一个变量、字段或集合元素拥有。
    例外:Rc<T>、Arc<T> 是显式共享所有权,通过引用计数管理,但每个句柄仍然是强引用所有者之一。

  2. 同一时间只能有一个所有者
    如果发生赋值、传参、返回等操作,所有权可能被移动,旧所有者不能再使用该值。

  3. 所有者离开作用域,值被丢弃
    如果值实现了 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 常见于:

  1. 变量赋值

    let s1 = String::from("hello");
    let s2 = s1; // s1 被移动,s1 失效
    
  2. 函数传参

    fn take(s: String) {}
    let s = String::from("a");
    take(s); // s 被移动进函数
    
  3. 函数返回

    fn give() -> String {
        String::from("x") // 所有权返回给调用者
    }
    
  4. 结构体 / 元组 / 枚举解构或字段赋值

    struct P { name: String, age: u8 }
    let p = P { name: String::from("A"), age: 20 };
    let name = p.name; // p.name 被移动
    println!("{}", p.age); // 部分移动后,未移动字段仍可用
    
  5. 闭包 move 捕获

    let s = String::from("a");
    let c = move || println!("{}", s); // s 所有权进入闭包
    
  6. 迭代器消费

    let v = vec![String::from("a")];
    for s in v { // v 被移动,元素所有权逐个转移
        println!("{}", s);
    }
    
  7. 集合操作
    Vec::pop、Vec::remove、Vec::into_iter 等会转移元素所有权。
    注意:不能直接 let x = v[0]; 移动出 Vec 元素,因为 Vec 需要完整释放;可用 remove、swap_remove、into_iter 等。

  8. 线程与智能指针
    thread::spawn(move || ...) 需要把所有权移入新线程。
    Box<T>、Rc<T>、Arc<T> 等句柄本身移动时,转移的是句柄所有权。


四、Move 的内部机制

Rust 的 Move 不是运行时“搬走堆数据”,而是:

  1. 编译期:借用检查器跟踪每个变量、字段是否已初始化、是否已被移动。
  2. 运行时:通常只按位复制栈上的表示,比如 String 的指针、长度、容量。
  3. 源失效:旧所有者被标记为“已移动”,不能再使用、借用或再次移动。
  4. 释放责任转移:新所有者离开作用域时负责 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,也能安全管理堆内存

理解所有权后,再去看借用、生命周期、智能指针、闭包和并发,都会顺很多。

posted @ 2026-10-08 13:56  悠哉大斌  阅读(7)  评论(0)    收藏  举报