Skip to the content.

Rust 中 Move、Copy、Clone 和 Drop 的关系

看 stackoverflow 的这个回答说明核心区别:

简单来说 Clone 和 Copy 本质就是深度拷贝和潜拷贝 Copy 就是简单的 memcpy

可以把 CopyCloneDrop 放到 Rust 的所有权体系里理解。它们分别回答三个问题:

概念 回答的问题 是否显式调用 是否允许自定义行为
Move 所有权转交给谁 隐式
Copy 移动时能否保留原值 隐式
Clone 能否主动创建一个新值 显式 .clone()
Drop 值失效时如何清理 隐式

可以解释

  1. Copy 不可以自定义,因为 memcpy 没有什么操作空间
  2. Copy 和 Drop 是冲突的
  3. Clone 和 Drop 可以同时存在

因为 每次 clone() 都必须产生一个能够独立完成其销毁责任的新值, 要么深度拷贝,要么增加引用计数,所以新的对象就是让 Drop 来操作的

  1. Copy 和 Drop 为什么冲突
    • Copy 制作了两个对象,但是 Drop 两次,很有可能是 double free

= 的含义

右侧类型实现 Copy => 复制,左右两边都能继续使用 右侧类型没有实现 Copy => move,右侧原位置失效

= 本身不会调用 Clone。

Move 是默认行为

Move 不是 trait,而是 Rust 默认的所有权转移方式。

let a = String::from("hello");
let b = a;

// println!("{a}"); // 错误:a 已经被移动
println!("{b}");

这里没有复制字符串,只是把字符串的所有权从 a 转交给了 b。作用域结束时,只会销毁 b

a --move--> b --drop-->

这保证堆内存只释放一次。

即使一个类型没有实现 Drop,它的字段仍然可能需要销毁:

struct User {
    name: String,
}

User 没有手动实现 Drop,但编译器会生成 drop glue,在 User 销毁时自动销毁 name

因此要区分:

典型类型对照

类型 Copy Clone 需要清理 说明
i32 普通数值
&T 复制共享引用
&mut T 避免产生多个可变引用
String Clone 深复制,离开作用域时释放内存
Vec<T> T: Clone 管理堆内存
Box<T> T: Clone 独占堆对象
Rc<T> Clone 增计数,销毁时减计数
Arc<T> 线程安全引用计数
File 销毁时关闭文件,复制需要 try_clone()
MutexGuard 销毁时解锁
Copy 字段结构体 可以 可以 适合派生 Copy

File 是一个值得注意的例子。复制文件句柄有多种可能语义,成本和平台行为也不简单,所以它没有实现普通的 Clone,而是提供可能失败的 try_clone()

drop() 和 Drop::drop() 的区别

通常不能直接调用析构方法:

value.drop();

应该使用:

std::mem::drop(value);

std::mem::drop() 本身非常简单,本质是取得值的所有权,然后让它立即离开作用域:

pub fn drop<T>(_x: T) {}

例如提前释放锁:

let guard = mutex.lock().unwrap();

// 使用受保护的数据

drop(guard); // 提前解锁

Drop::drop(&mut self) 是编译器在值销毁过程中调用的析构方法。

析构顺序和移动

局部变量通常按照声明顺序的逆序销毁:

let a = String::from("a");
let b = String::from("b");

// 先销毁 b,再销毁 a

结构体字段则按照定义顺序销毁,而不是逆序。编译器还会使用 drop flags 记录值或字段是否已经被移动,避免再次销毁已经转移所有权的内容。

手动实现了 Drop 的类型不能随意移出某个字段:析构函数运行时通常假设整个 self 仍然完整。需要取出字段时,常用 Option::take()mem::replace() 或更底层的手动析构方案。

与析构有关的其他概念

std::mem::needs_drop::()

用于判断类型是否可能需要析构:

use std::mem::needs_drop;

assert!(!needs_drop::<i32>());
assert!(needs_drop::<String>());

它主要适合底层容器和 unsafe 代码做优化,普通业务代码通常不需要使用。

ManuallyDrop

禁止编译器自动销毁内部值:

use std::mem::ManuallyDrop;

let value = ManuallyDrop::new(String::from("hello"));

这常用于 unsafe、联合体或手动管理字段销毁顺序。它不会让一个原本非 Copy 的资源安全地变成 Copy;开发者必须自己保证资源最终只释放一次。

mem::forget()

消耗一个值,但不运行其析构函数:

std::mem::forget(String::from("hello"));

这会泄漏资源。它是安全函数,因为 Rust 的内存安全不能依赖析构一定执行,例如程序可能中止,引用计数也可能形成环。

MaybeUninit

表示一块可能尚未初始化为 T 的存储:

use std::mem::MaybeUninit;

let slot = MaybeUninit::<String>::uninit();

未初始化内容不会被当作 T 自动销毁。它主要用于底层初始化、FFI 和 unsafe 容器实现。

实用判断方法

设计类型时可以这样考虑:

最终可以归纳为:

Move:转移同一个所有权
Copy:无条件、隐式地复制普通值
Clone:显式创建新的所有权或共享关系
Drop:结束一份所有权并执行清理

Clone 能在复制过程中建立新的资源关系,所以可以和 Drop 配合;Copy 没有机会执行这些逻辑,所以不能和 Drop 同时存在。

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。