为何`std::mem::forget`无法创建静态引用?代码崩溃解析
Rust代码段错误原因分析与修正
问题代码
pub trait Forget { fn forget_me_as_ptr(self) -> *const Self where Self: Sized, { let p = addr_of!(self); forget(self); p } fn forget_me_as_mut_ptr(self) -> *mut Self where Self: Sized, { self.forget_me_as_ptr() as *mut Self } fn forget_me_as_ref(self) -> &'static Self where Self: Sized, { self.forget_me_as_mut_ref() } fn forget_me_as_mut_ref(mut self) -> &'static mut Self where Self: Sized, { let p = addr_of_mut!(self); forget(self); unsafe { p.as_mut().unwrap() } } } pub fn default<T: Forget + Default>() -> &'static T { T::default().forget_me_as_ref() } #[derive(Debug)] struct Player { health: u8, name: String, aliases: Vec<String>, } impl Drop for Player { fn drop(&mut self) { println!("Dropping [{}, {self:p}]", type_name::<Self>()); } } impl Display for Player { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "[{}, {self:p}] = name: {}, health: {}, aliases: {:#?}", type_name::<Self>(), self.name, self.health, self.aliases ) } } impl Forget for Player {} impl Default for Player { fn default() -> Self { Self { health: 100, name: "default".into(), aliases: vec!["default".to_string()], } } } fn main() { let p = default::<Player>(); println!("{p}"); }
运行输出
$ cargo r Finished dev [unoptimized + debuginfo] target(s) in 0.00s Running `target/debug/static_factory` Segmentation fault (core dumped)
问题分析
段错误直接原因
你拿到的指针指向的是栈上的临时对象。T::default()创建的Player实例分配在default函数的栈帧中,当forget_me_as_mut_ref执行完毕后,该函数栈帧会被销毁,栈上的内存会被回收或覆盖。虽然std::mem::forget跳过了Drop析构逻辑,但它无法阻止栈内存被系统回收——栈内存的生命周期完全由栈帧决定,函数返回后,对应内存就不再属于原对象。后续通过指针转成&'static引用访问这块内存,本质是访问已失效的栈内存,直接触发段错误。
逻辑漏洞
你误解了std::mem::forget的作用:
forget仅告知Rust不要执行对象的Drop方法,不会改变对象所在内存的生命周期。- 栈上的对象无论是否被
forget,只要栈帧销毁,内存就会被回收;只有堆上分配的对象,在forget后才会脱离Rust自动内存管理,但前提是对象本身存储在堆上。 - 直接将栈对象的指针转为
'static引用完全错误,'static引用要求指向的内存从程序启动到结束始终有效,栈内存显然不满足这个条件。
引用、实例、指针的关系
- 实例:数据的实际存储载体,分为栈实例和堆实例。栈实例存在于函数栈帧中,随栈帧销毁而消失;堆实例由
Box、String、Vec等类型分配,内存由Rust所有权系统管理,当所有者被销毁且未被forget时,堆内存会被自动释放。 - 指针:内存地址的裸值,Rust不对裸指针做任何生命周期或有效性检查,你可以用它指向任意内存,但访问时必须自行保证内存有效。
- 引用:受Rust安全规则约束的指针,必须指向有效内存,且生命周期必须与被引用对象的生命周期匹配。
&'static引用要求指向的内存全程有效,比如全局变量、用Box::leak泄漏到堆上的对象。
代码修正
要创建'static引用,正确做法是将对象分配到堆上,再通过泄漏堆内存让其脱离自动管理。可以直接用Rust标准库的Box::leak实现,无需自定义Forget trait:
use std::fmt; use std::any::type_name; pub fn default<T: Default>() -> &'static T { Box::leak(Box::new(T::default())) } #[derive(Debug)] struct Player { health: u8, name: String, aliases: Vec<String>, } impl Drop for Player { fn drop(&mut self) { println!("Dropping [{}, {self:p}]", type_name::<Self>()); } } impl fmt::Display for Player { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "[{}, {self:p}] = name: {}, health: {}, aliases: {:#?}", type_name::<Self>(), self.name, self.health, self.aliases ) } } impl Default for Player { fn default() -> Self { Self { health: 100, name: "default".into(), aliases: vec!["default".to_string()], } } } fn main() { let p = default::<Player>(); println!("{p}"); }
修正说明
Box::new(T::default())将Player实例分配到堆上,Box作为堆对象的所有者。Box::leak消耗Box并返回&'static mut T(可自动转为&'static T),同时泄漏堆内存——这块内存不会被Rust自动释放,会一直存在到程序结束,满足'static生命周期要求。- 修正后的代码运行时不会触发段错误,因为访问的是堆上的有效内存。
内容的提问来源于stack exchange,提问作者Self
相关产品推荐
相关产品推荐

