Есть ли способ сообщить проверяющему удаление Rust, что мы эффективно владеем `T`, не указывая его в общих параметрах?

#rust #lifetime #borrow-checker

#Ржавчина #время жизни #заимствование-проверка

Вопрос:

Допустим, я хочу написать такой код:

 struct Inspector<'a>(amp;'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(amp;foo.value));
}
 

игровая площадка

В настоящее время компилятор Rust позволяет мне делать это до тех пор, пока я не добавлю Drop реализацию для Inspector .

Если я добавлю его, возникает следующая ошибка времени компиляции:

foo.value удалено, пока все еще заимствовано.

Заимствование может использоваться, когда foo отбрасывается и запускает деструктор для типа Foo<'_>

И это, очевидно, правильно. На самом деле, я взял этот пример из nomicon.

Итак, вот моя проблема. Допустим, у меня странная реализация a 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>(amp;mut self) -> amp;mut T {
        amp;mut *self.data.cast().as_ptr()
    }
}

impl Drop for MyBox {
    fn drop(amp;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 не знает, что MyBox оно удалит a T при вызове его деструктора. Это позволяет мне писать этот неправильный код:

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

impl Drop for Inspector<'_> {
    fn drop(amp;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: amp;mut Foo = unsafe { b.trust_mut() };
    foo.inspector = Some(Inspector(amp;foo.value)); // No error occurs here
}
 

игровая площадка

Исходя из этого, мой вопрос прост: есть ли способ сообщить проверяющему удаление, что не нормально иметь время жизни, ограниченное объектом, когда он отбрасывается? Потому что мне в основном нужно что-то вроде PhantomData<T> без наличия T .

Комментарии:

1. Ах .. еще одна проблема с самореферентной структурой.

Ответ №1:

Я могу делать с этим и другие неприятные вещи MyBox , которые не имеют к этому никакого Drop отношения …

 fn main() {
    let mut vec: Vec<i32> = vec![42];
    let mut b = MyBox::new(amp;vec[0]); // T is amp;i32
    {
        let val: amp;mut amp;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: amp;mut amp;i32 = unsafe { b.trust_mut() };
        println!("{}", *val);
    }
}
 

Проблема в том, что MyBox полностью стирается любая информация о заимствованиях T . Есть два способа исправить это:

  • Строгий вариант — изменить <T> на <T: 'static> in MyBox::new и MyBox::trust_mut . Это предотвратит заимствование значения in T каких-либо данных, отличных 'static от данных.
  • Гибкий вариант заключается в добавлении параметра времени жизни в MyBox , а затем изменении <T> на <T: 'a> в MyBox::new и MyBox::trust_mut . Если вам нужны эффекты строгой опции, просто используйте MyBox<'static> .
 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<amp;'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>(amp;mut self) -> amp;mut T {
        amp;mut *self.data.cast().as_ptr()
    }
}