5 个版本
0.2.3 | 2023年3月4日 |
---|---|
0.2.2 | 2023年3月4日 |
0.2.1 | 2023年3月3日 |
0.2.0 | 2023年3月3日 |
0.1.0 | 2023年3月1日 |
2030 在 Rust 模式
27 每月下载量
12KB
167 行
如果 dyn
是已知大小呢?
在正常使用 async
(无论是 std
还是 core
)时,您可以连续编写
async fn async_things(...) -> T {}
// it is equivalent to:
fn async_things(...) -> impl Future<Output=T> {}
这是可以接受的,因为 impl
可以是任何类型,我们并不关心它在哪里以及它的内存是如何释放的。
直到你想要 `async trait'。想象一下,你有一个这样的特质
mod io {
type Result<T> = ...;
}
pub trait AsyncRead {
async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
...
}
之前你可能必须像这样重写 `read'
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8]
) -> Poll<io::Result<usize>>;
但现在你可以这样写
fn read(&mut self, buf: &mut [u8]) -> impl Future<Output=io::Result<usize>>;
// aka: async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
这为每个单独的实现创建了一个新的类型,实现了 Future
,所以它不是 object-safety
error[E0038]: the trait `AsyncRead` cannot be made into an object
--> src\main.rs:20:12
|
20 | let _: dyn AsyncRead = todo!();
| ^^^^^^^^^^^^^ `AsyncRead` cannot be made into an object
|
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
--> src\main.rs:16:43
|
15 | pub trait AsyncRead {
| --------- this trait cannot be made into an object...
16 | fn read(&mut self, buf: &mut [u8]) -> impl Future<Output = io::Result<usize>>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because method `read` references an `impl Trait` type in its return type
= help: consider moving `read` to another trait
问题:核心中没有内存分配器
显然的解决方案是使用 Box<dyn Future>
,但在 no-std
系统(其中仅提供 core
和 alloc
)中,Box
硬编码似乎是多余的,因为它需要分配器。
问题:&dyn Traits 不能替换 impl Traits
但如果有一个类型允许您保存与指针大小相同的大小,以及存储指向 drop
的指针的 Vtable,那么我们就会得到一种新的胖指针,它作为 T: 'a' (与
&'a T' of `&dyn Trait')
基本用法
在今天的 rast 中,没有机制可以将 dyn Trait
传递给 impl Trait
fn print(x: impl Display) {
println!(“{x}”);
}
并且我们可以轻松地在 'static
上下文中使用它
fn print_later(x: impl Display + Send + 'static) {
thread::spawn(move || println!("{x}"));
}
但是,我们如何在这里通过值传递而不是引用传递来发送 &dyn Trait 呢?当然,使用 Box
。
fn print_later(x: Box<dyn Display + Send>) {
thread::spawn(move || println!("{x}"));
}
然而,这总是需要内存分配,这在很多情况下(和在 no_std
中几乎总是)是没有意义的。
如果你只是存储 dyn Trait
会怎样呢?
但它需要多少空间?它是如何对齐的?你必须在编译时知道所有这些,这绝对不是关于 dyn
的。
那么,你如何选择大小呢?
让我们记住一个事实——Box<T>
是透明的指针。那么,如果你想象我们的新类型,它看起来可能就像这样
struct DynPtr<Dyn: ?Sized> {
repr: PtrRepr,
// is alias to `*const ()` not to be confused
drop: unsafe fn(*const ()),
}
这让你想起什么吗?这与在 std
中创建 vtables 的最流行方式非常相似。这是为 Box
的 DynPtr
的原始创建可能的样子
let ptr = DynPtr::<dyn Display> {
repr: Box::into_raw(x) as PtrRepr,
drop: |repr| unsafe {
let _ = Box::from_raw(repr as *mut i32);
},
};
(ptr.drop)(ptr.repr); // drop original box
你可能注意到,from_raw
/into_raw
可以推广到 mem::transmute
的情况,因为 Box 相似指针的 repr 类似于 *const ()
。
此外,这种方法还有一个优点,即实际上许多 future 都包含一个非常小的状态(与指针相等或更小),或者它的状态也被封装在 Arc
-like 中。这意味着在一般情况下,你可以继续使用 Box
,它也相当于一个指针,对于足够瘦的原始类型使用你自己的转换。
这就是 Dyn<dyn Trait>
的出现方式(这里的 dyn
是由于 Rust 2021
版本的要求的),它可以像 impl Trait
一样容易使用
fn print_later(x: Dyn<dyn Display + Send + 'static>) {
thread::spawn(move || println!("{}", &*x)); // `Dyn` is not automatically delegate traits like `dyn`
}
// use `.do_dyn()` to coerce type into `Dyn<dyn ...>`
let x = & 12;
print_later(x.do_dyn());
print_later(12_usize.do_dyn());
print_later(Box::new(x).do_dyn());