我正在从标准库中实现我自己的
Box<T>
以学习如何编写不安全的代码。
我的实现看起来像:
use std::marker::PhantomData;
use std::{alloc, mem, ptr};
use ptr::NonNull;
use alloc::Layout;
pub struct MyBox<T> {
data: NonNull<T>,
_marker: PhantomData<T>,
}
impl<T> MyBox<T> {
pub fn new(data: T) -> MyBox<T> {
unsafe {
let layout = Layout::for_value(&data);
let ptr = alloc::alloc(layout) as *mut T;
ptr::write(ptr, data);
let ptr = NonNull::new(ptr).unwrap_or_else(|| alloc::handle_alloc_error(layout));
MyBox {
data: ptr,
_marker: PhantomData,
}
}
}
}
impl<T> Drop for MyBox<T> {
fn drop(&mut self) {
unsafe {
let ptr = self.data.as_ptr();
drop(ptr::read(ptr));
alloc::dealloc(ptr as *mut u8, Layout::new::<T>())
}
}
}
如您所见,我使用了不同的方法来获取布局,但是,
dealloc
要求我们使用与分配值相同的布局。
根据这些方法的源代码,
Layout::for_value
使用mem::size_of_val
和mem::align_of_val
,而Layout::new
使用mem::size_of
和mem::align_of
。据我从文档中掌握的信息,只有当 T 是动态大小类型 (DST) 时它们才会不同。因此,就我仅针对大小类型实施MyBox
(除非明确指定,否则它们实际上是)我应该没问题,不是吗?
来自
std::mem::size_of_val
的文档:
这通常与
相同。然而,当size_of::<T>()
没有 静态已知大小,例如切片 [T
][slice] 或 [trait object], 然后[T]
可用于获取动态已知的大小。size_of_val
默认情况下,当您引入类型参数
T
时,就像您在 MyBox<T>
中所做的那样,会有一个隐含的 T: Sized
约束。这意味着 mem::size_of_val
和 mem::size_of
将返回相同的东西。
为了支持未调整大小的
T
,你需要明确说明:
pub struct MyBox<T> where T: ?Sized {
// ...
}
和:
impl<T> Drop for MyBox<T> where T: ?Sized {
fn drop(&mut self) {
// ...
}
}
请注意,您的
new
函数仍然需要具有 T: Sized
,因为它需要按值接受它作为参数。但是,您可能会找到其他方法来为 DST 构建 MyBox
。
随着这个变化,你现在不能使用
Layout::new<T>()
,因为它需要T: Sized
。相反,您需要使用 Layout
构建 for_value
,它没有 Sized
约束。
pub struct MyBox<T: ?Sized> {
data: NonNull<T>,
_marker: PhantomData<T>,
}
impl<T: ?Sized> Drop for MyBox<T> {
fn drop(&mut self) {
unsafe {
let ptr = self.data.as_ptr();
let layout = Layout::for_value(unsafe { &*ptr } );
ptr.drop_in_place();
alloc::dealloc(ptr as *mut u8, layout)
}
}
}
您还需要将
drop(ptr::read(ptr));
更改为 ptr.drop_in_place()
- 部分是因为它更好,但 主要是 因为 drop
也需要 T: Sized
:)