【问题标题】:Is there a way to tell the Rust's drop checker we effectively own a `T` without it being in generic parameters?有没有办法告诉 Rust 的 drop checker 我们有效地拥有一个 `T` 而不是泛型参数?
【发布时间】:2021-04-28 04:26:21
【问题描述】:

假设我想写这样的代码:

struct Inspector<'a>(&'a u8);

struct Foo<'a> {
    value: Box<u8>,
    inspector: Option<Inspector<'a>>,
}

fn main() {
    let mut foo = Foo { value: Box::new(0), inspector: None };
    foo.inspector = Some(Inspector(&foo.value));
}

playground

目前,只要我不为 Inspector 添加 Drop 实现,Rust 编译器就允许我这样做。

如果我添加一个,会出现以下编译时错误:

foo.value 在借用时丢弃了。

foo 被删除并运行Foo&lt;'_&gt; 类型的析构函数时,可能会使用借用

这显然是正确的。其实这个例子我取自the nomicon

现在,这是我的问题。假设我有一个 Box 的奇怪实现,它的类型参数中没有任何 T

/// An heap-allocated `T` without generic parameters.
struct MyBox {
    data: NonNull<u8>,

    /// SAFETY:
    /// Caller must ensure the value will not be
    /// used again.
    drop_fn: unsafe fn(*mut u8),

    layout: Layout,
}

impl MyBox {
    fn new<T>(val: T) -> Self {
        if mem::size_of::<T>() == 0 {
            panic!("T is a ZST");
        }

        let layout = Layout::new::<T>();
        let data = NonNull::new(unsafe { alloc(layout) })
            .unwrap_or_else(|| handle_alloc_error(layout));

        // pointer refers to uninit owned memory
        unsafe { data.cast::<T>().as_ptr().write(val) };

        Self {
            data,
            // SAFETY: See `drop_fn` field for safety guarantees
            drop_fn: |data| unsafe { drop_in_place(data as *mut T) },
            layout,
        }
    }

    /// Caller must ensure that this box owns a `T`.
    unsafe fn trust_mut<T>(&mut self) -> &mut T {
        &mut *self.data.cast().as_ptr()
    }
}

impl Drop for MyBox {
    fn drop(&mut self) {
        // SAFETY: Value will not be used again
        unsafe { (self.drop_fn)(self.data.as_ptr()) }
        unsafe { dealloc(self.data.as_ptr(), self.layout) };
    }
}

但这一次,Rust 的 drop checker 并不知道 MyBox 在调用其析构函数时会删除 T。这使我能够编写这个不健全的代码:

pub struct Inspector<'a>(&'a u8);

impl Drop for Inspector<'_> {
    fn drop(&mut self) {
        /* Could try to inspect `self.0` here which might have been dropped */
    }
}

pub struct Foo<'a> {
    value: Box<u8>,
    inspector: Option<Inspector<'a>>,
}

fn main() {
    let mut b = MyBox::new(Foo {
        value: Box::new(0),
        inspector: None,
    });

    let foo: &mut Foo = unsafe { b.trust_mut() };
    foo.inspector = Some(Inspector(&foo.value)); // No error occurs here
}

playground

由此,我的问题很简单:有没有办法告诉 drop checker 在对象被丢弃时不能将其生命周期绑定到对象上?因为我基本上需要的是像PhantomData&lt;T&gt; 这样没有T 的东西。

【问题讨论】:

  • 啊..又一个自引用结构问题。

标签: rust lifetime borrow-checker


【解决方案1】:

我可以用这个MyBox 做其他与Drop 无关的讨厌的事情...

fn main() {
    let mut vec: Vec<i32> = vec![42];
    let mut b = MyBox::new(&vec[0]); // T is &i32
    {
        let val: &mut &i32 = unsafe { b.trust_mut() };
        println!("{}", *val);
    }
    vec[0] = 1337; // I can mutate `vec[0]` even though `vec[0]` is borrowed by `b`
    {
        let val: &mut &i32 = unsafe { b.trust_mut() };
        println!("{}", *val);
    }
}

问题在于MyBox 完全删除了T 中有关借用的任何信息。有两种方法可以解决此问题:

  • 严格的选项是在MyBox::newMyBox::trust_mut 中将&lt;T&gt; 更改为&lt;T: 'static&gt;。这将防止T 中的值借用任何非'static 数据。
  • 灵活的选择是给MyBox添加一个生命周期参数,然后在MyBox::newMyBox::trust_mut中将&lt;T&gt;更改为&lt;T: 'a&gt;。如果您想要严格选项的效果,只需使用MyBox&lt;'static&gt;
use std::alloc::{Layout, alloc, dealloc, handle_alloc_error};
use std::marker::PhantomData;
use std::mem::size_of;
use std::ptr::{self, NonNull};

/// An heap-allocated `T` without generic parameters.
struct MyBox<'a> {
    data: NonNull<u8>,

    /// SAFETY:
    /// Caller must ensure the value will not be
    /// used again.
    drop_fn: unsafe fn(*mut u8),

    layout: Layout,

    _borrow: PhantomData<&'a ()>,
}

impl<'a> MyBox<'a> {
    fn new<T: 'a>(val: T) -> Self {
        if size_of::<T>() == 0 {
            panic!("T is a ZST");
        }

        let layout = Layout::new::<T>();
        let data = NonNull::new(unsafe { alloc(layout) })
            .unwrap_or_else(|| handle_alloc_error(layout));

        // pointer refers to uninit owned memory
        unsafe { data.cast::<T>().as_ptr().write(val) };

        Self {
            data,
            // SAFETY: The caller must ensure that the value will not
            // be using the value again.
            drop_fn: |data| unsafe { ptr::drop_in_place(data as *mut T) },
            layout,
            _borrow: PhantomData,
        }
    }

    /// Caller must ensure that this box owns a `T`.
    unsafe fn trust_mut<T: 'a>(&mut self) -> &mut T {
        &mut *self.data.cast().as_ptr()
    }
}

【讨论】:

    猜你喜欢
    • 2021-10-15
    • 2015-11-04
    • 1970-01-01
    • 2016-04-26
    • 2023-03-31
    • 2016-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多