rust 为什么`std::mem::forget`不能用于创建静态引用?

xvw2m8pv  于 2023-08-05  发布在  其他
关注(0)|答案(3)|浏览(123)
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)


在上面的代码片段中,为什么它是segfaulting?std::mem::forget只是获取所有权,并不运行析构函数/drop,而析构函数/drop可以用来创建生存期为静态的引用。这种逻辑的缺陷在哪里?我是Rust的新手,有点想了解引用,struct T的示例,指针之间的关系。上面的代码从一个天真的想法看起来逻辑上是正确的。请解释并更正。

ldfqzlk8

ldfqzlk81#

使用std::mem::forget可以避免运行Drop实现,但由于self是一个局部变量,它的内存仍然会被回收并用于其他事情。
要获得所需的行为,您需要将其移动到一个不会被重用的稳定地址。您可以通过将其放入Box中,获取指向它的指针,然后忽略该框来实现此目的。幸运的是,已经有一个函数可以实现:Box::leak。类似于:

pub trait Forget {
    // ... the rest ...

    fn forget_me_as_mut_ref(mut self) -> &'static mut Self
    where
        Self: Sized,
    {
        Box::leak(Box::new(self))
    }

字符串

fgw7neuy

fgw7neuy2#

局部变量,就像一个函数的参数一样,存在于堆栈中,无论drop是否在它的内存中被调用,都不会有什么不同,因为一旦你离开这个函数,任何指向它的指针都会变成悬空的。因此,当p指向本地内存(例如参数self)并返回结果时,unsafe { p.as_mut().unwrap() }是不可靠的。

w80xi6nr

w80xi6nr3#

我的嫌疑人是跟线的:

pub fn default<T: Forget + Default>() -> &'static T {
    T::default().forget_me_as_ref()
}

字符串
T::default()在堆栈上创建一个临时对象,我们为它创建一个引用。一旦函数调用结束,对应于堆栈分配对象的内存就被乱码了。如果对象是在heap上创建的,我认为这会消失。

相关问题