Rust 中的 needs_drop 是什么
太长不看
std::mem::needs_drop 用来判断数据类型的析构操作是否重要。对应于 C++ 的 std::is_trival,可用于容器类型的优化。
示例
Copy 类型
struct Point{
x: i32,
y: i32,
}
Point 类型完全由数字组成,当释放一个数组 [Point;N] 时,不需要对每个元素挨个调用 drop,而是直接释放整块内存。
assert_eq!(needs_drop::<Point>(), false);
assert_eq!(needs_drop::<[Point; 16]>(), false);
对于实现了 Copy 的类型,needs_drop 总是返回 false, 这样的类型通常都不持有资源,是平凡的,比如布尔类型、数字类型。
#[derive(Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
Drop 类型
struct MyBox<T> {
ptr: *mut T,
}
use std::alloc::{alloc, dealloc, Layout};
impl<T> MyBox<T> {
fn new(value: T) -> Self {
unsafe {
let ptr = alloc(Layout::new::<T>()) as *mut T;
std::ptr::write(ptr, value);
Self { ptr }
}
}
}
needs_drop::<MyBox>() 返回 false,MyBox 类型由一个裸指针组成,它的默认析构操作是不重要的。
然而,堆分配的内存必须释放,否则会引起内存泄露。我们需要手动实现 Drop。
impl<T> Drop for MyBox<T> {
fn drop(&mut self) {
unsafe {
std::ptr::drop_in_place(self.ptr);
dealloc(self.ptr as *mut u8, Layout::new::<T>())
}
}
}
std::ptr::drop_in_place 已经执行了 needs_drop 检查,会根据情况选择是否执行 drop。一般来说,我们不需要自己检查 needs_drop,调用 drop_in_place 是正确的选择。
对于平凡数据,这里相当于直接释放内存,对于非平凡数据,这里会正确执行原地析构后再释放内存。
手动实现 Drop 的类型通常拥有资源,删除这类数据必须先执行析构,needs_drop 对这类类型返回 true.
assert_eq!(needs_drop::<MyBox<String>>(), true);
assert_eq!(needs_drop::<MyBox<u8>>(), true);
对于其他类型,编译器会根据结构体字段推测析构操作的重要性,情况太多,不便一一解释。
关于 Drop 和 Copy
E0184 说明:同时实现 Drop 和 Copy 理论上有用,但目前的编译器实现有问题,会导致内存不安全 (issue #20126),因此不允许。Drop 和 Copy 中只能实现一个。
E0204说明:如果想为结构体实现 Copy ,那么它的字段必须都是 Copy.
关于 ManuallyDrop
std::mem::ManuallyDrop<T> 阻止编译器执行 T 的析构操作。它的背后是语言项,编译器做了特殊处理。
needs_drop 对 ManuallyDrop<T> 返回 false
assert_eq!(needs_drop::<ManuallyDrop<u8>>(), false);
assert_eq!(needs_drop::<ManuallyDrop<String>>(), false);
包含 ManuallyDrop<T> 的类型通常使用了 unsafe ,会手动实现 Drop,以便自定义字段析构顺序和行为。
关于 LLVM
nomicon 中提到,LLVM 擅长消除无副作用的代码。
needs_drop 的签名是 pub const fn needs_drop<T>() -> bool
,背后是编译器内联函数。使用它的条件分支跳转实际上可以在编译期确定,无用分支会被消除。
drop_in_place 背后是语言项,显然存在相关优化。
nomicon 有时被称为 死灵书。这本书介绍了 Unsafe Rust 的细节知识,带读者进入 Rust 黑魔法世界。