#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>
inMyBox::new
иMyBox::trust_mut
. Это предотвратит заимствование значения inT
каких-либо данных, отличных'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()
}
}