有没有办法告诉 Rust 的丢弃检查器我们有效地拥有一个 `T` 而无需它在通用参数中?
Is there a way to tell the Rust's drop checker we effectively own a `T` without it being in generic parameters?
假设我想编写这样的代码:
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));
}
目前,只要我不为 Inspector
添加 Drop
实现,Rust 编译器就允许我这样做。
如果我添加一个,会出现以下编译时错误:
foo.value
dropped while still borrowed.
Borrow might be used when foo
is dropped and runs the destructor for type Foo<'_>
这显然是对的。事实上,我已经从 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 的丢弃检查器不知道 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
}
据此,我的问题很简单:有没有办法告诉丢弃检查器,当对象被丢弃时,将生命周期限制在对象上是不行的?因为我基本上需要的是 PhantomData<T>
而没有 T
.
我可以用这个 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::new
和MyBox::trust_mut
中将<T>
改为<T: 'static>
。这将防止 T
中的值借用任何非 'static
数据。
- 灵活的选择是在
MyBox
中添加一个生命周期参数,然后在MyBox::new
和MyBox::trust_mut
中将<T>
改为<T: 'a>
。如果你想要严格选项的效果,只需使用 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<&'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()
}
}
假设我想编写这样的代码:
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));
}
目前,只要我不为 Inspector
添加 Drop
实现,Rust 编译器就允许我这样做。
如果我添加一个,会出现以下编译时错误:
foo.value
dropped while still borrowed.Borrow might be used when
foo
is dropped and runs the destructor for typeFoo<'_>
这显然是对的。事实上,我已经从 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 的丢弃检查器不知道 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
}
据此,我的问题很简单:有没有办法告诉丢弃检查器,当对象被丢弃时,将生命周期限制在对象上是不行的?因为我基本上需要的是 PhantomData<T>
而没有 T
.
我可以用这个 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::new
和MyBox::trust_mut
中将<T>
改为<T: 'static>
。这将防止T
中的值借用任何非'static
数据。 - 灵活的选择是在
MyBox
中添加一个生命周期参数,然后在MyBox::new
和MyBox::trust_mut
中将<T>
改为<T: 'a>
。如果你想要严格选项的效果,只需使用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<&'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()
}
}