为何PhantomData在Rust析构检查(Drop Check)中未生效?
按照Rust的Drop Check规则,以下使用自定义Uniqu结构体(包含PhantomData)的代码本应编译失败,但实际却编译通过:
use core::marker::PhantomData; fn main() { struct Inspector<'a>(&'a u8); impl<'a> Drop for Inspector<'a> { fn drop(&mut self) { println!("I was only {} days from retirement!", self.0); } } struct Uniqu<T> { ptr: *mut T, marker: PhantomData<T>, } struct World<'a> { ptr: Option<Uniqu<Inspector<'a>>>, days: u8, } let mut world = World{ptr: None, days:1}; let mut inspector = Inspector(&world.days); let mut uniqu = Uniqu::<Inspector>{ptr: &mut inspector, marker: PhantomData}; world.ptr = Some(uniqu); }
而将Uniqu替换为Box后,代码触发了预期的编译错误,Drop Check正常工作:
use core::marker::PhantomData; fn main() { struct Inspector<'a>(&'a u8); impl<'a> Drop for Inspector<'a> { fn drop(&mut self) { println!("I was only {} days from retirement!", self.0); } } struct World<'a> { ptr: Option<Box<Inspector<'a>>>, days: u8, } let mut world = World{ptr: None, days:1}; let mut inspector = Inspector(&world.days); world.ptr = Some(Box::new(inspector)); }
错误信息:
error[E0597]: `world.days` does not live long enough --> src/main.rs:21:35 | 21 | let mut inspector = Inspector(&world.days); | ^^^^^^^^^^^ borrowed value does not live long enough ... 26 | } | - | | | `world.days` dropped here while still borrowed | borrow might be used here, when `world` is dropped and runs the destructor for type `World<'_>`
原因分析
核心差异在于编译器对两种类型的Drop行为判断:
Drop Check的本质
Rust的Drop Check规则是为了防止对象销毁时,其内部的引用访问已经被销毁的数据。编译器只会对那些在销毁过程中可能访问引用的类型执行严格的生命周期检查。Box<T>的Drop行为被编译器识别Box<T>是标准库提供的智能指针,它的Drop实现会主动销毁内部的T:如果T实现了Droptrait(比如例子中的Inspector),Box的Drop过程会调用T的Drop方法。编译器明确知道这一点,因此会将Box<Inspector<'a>>的销毁过程纳入Drop Check分析——当world被销毁时,Box中的Inspector会执行Drop,而Inspector持有&world.days的引用,此时world.days可能已经被销毁,导致悬垂引用,因此编译器拦截了这个违规情况。自定义
Uniqu<T>的Drop行为未触发检查
你的Uniqu结构体没有手动实现Droptrait,使用的是Rust自动生成的默认Drop逻辑:
- 字段
ptr是裸指针*mut T,裸指针的销毁不会执行任何代码,也不会触发T的Drop; - 字段
marker是PhantomData<T>,它仅作为类型标记,销毁时无任何操作。
编译器判断Uniqu<Inspector<'a>>的销毁过程不会调用Inspector的Drop方法,自然也就不会访问Inspector中的引用,因此不需要执行严格的Drop Check,代码得以编译通过。
如果给Uniqu手动实现Drop,让它在销毁时调用内部T的Drop:
use core::ptr; impl<T> Drop for Uniqu<T> { fn drop(&mut self) { unsafe { ptr::drop_in_place(self.ptr); } } }
此时编译器会识别到Uniqu的Drop会触发Inspector的Drop,进而访问引用,就会和Box的情况一样触发编译错误。
内容的提问来源于stack exchange,提问作者dd.ho

