Welcome! This document explores how to combine dyn
and impl Trait
in return position. This is crucial pre-requisite for async functions in traits. As a motivating example, consider the trait AsyncIterator
:
trait AsyncIterator {
type Item;
async fn next(&mut self) -> Option<Self::Item>;
}
The async fn
here is, of course, short for a function that returns impl Future
:
trait AsyncIterator {
type Item;
fn next(&mut self) -> impl Future<Output = Option<Self::Item>>;
}
The focus of this document is on how we can support dyn AsyncIterator
. For an examination of why this is difficult, see this blog post.
Here is a high-level summary of the key details of our approach:
&mut dyn AsyncIterator
, same as any other trait.impl AsyncIterator for MyType
, same as any other trait.-> impl Trait
will allocate a Box
to store the trait, but only when invoked through a dyn Trait
(static dispatch is unchanged).-> impl Trait
function is dispatch through dyn
. We show how to implement an InlineAsyncIterator
type, for example, that wraps another AsyncIterator
and stores the resulting futures on pre-allocated stack space.
dyner
, that provides several common strategies.dyn AsyncIterator
do not need to know (or care) whether the impl allocates a box or uses some other allocation strategy.AsyncIterator
can just write an impl
. That code can be used with any number of allocation adapters.Let's start with how we expect to use dyn AsyncIterator
. This section will also elaborate some of our desiderata[1], such as the ability to use dyn AsyncIterator
conveniently in both std and no-std scenarios.
dyn
argumentWe expect people to be able to write functions that take a dyn AsyncIterator
trait as argument in the usual way:
async fn count(i: &mut dyn AsyncIterator) -> usize {
let mut count = 0;
while let Some(_) = i.next().await {
count += 1;
}
count
}
One key part of this is that we want count
to be invokable from both a std and a no-std environment.
This, too, looks like you would expect.
struct YieldingRangeIterator {
start: u32,
stop: u32,
}
impl AsyncIterator for YieldingRangeIterator {
type Item = u32;
async fn next(&mut self) {
if self.start < self.stop {
let i = self.start;
self.start += 1;
tokio::thread::yield_now().await;
Some(i)
} else {
None
}
}
}
count
in stdYou invoke it as you normally would, by performing an unsize coercion. Invoking the method requires an allocator by default.
let x = YieldingRangeIterator::new(...);
let c = count(&mut x /* as &mut dyn AsyncIterator */).await;
count
in no-std or other specialized casesYou need a "preallocating" wrapper or other strategy. This takes the form of a struct defined for each trait that wraps a impl YourTrait
(e.g., impl AsyncIterator
) and customizes the allocation strategy for the returned future. The dyner
crate packages up some common strategies and structs for standard traits.
For example, to pre-allocate the next
future on the stack, which is useful both for performance or no-std scenarios, you could use an "inline" adapter type, like the InlineAsyncIterator
type provided by the dyner
crate:
use dyner::InlineAsyncIterator;
// ^^^^^^^^^^^^^^^^^^^
// Inline adapter type
async fn count_range(mut x: YieldingRangeIterator) -> usize {
// allocates stack space for the `next` future:
let inline_x = InlineAsyncIterator::new(x);
// invoke the `count` fn, which will no use that stack space
// when it runs
count(&mut inline_x).await
}
Dyner provides some other strategies, such as the CachedAsyncIterator
(which caches the returned Box
and re-uses the memory in between calls) and the BoxInAllocatorAsyncIterator
(which uses a Box
, but with a custom allocator).
The InlineAsyncIterator
adapts an AsyncIterator
to pre-allocate stack space for the returned futures, but what if you want to apply that inline stategy to one of your traits? You can do that by using the #[inline_adapter]
attribute macro applied to your trait definition:
#[inline_adapter(InlineMyTrait)]
trait MyTrait {
async fn some_function(&mut self);
}
This will create an adapter type called InlineMyTrait
(the name is given as an argument to the attribute macro). You would then use it by invoking new
:
fn foo(x: impl MyTrait) {
let mut w = InlineMyTrait::new(x);
bar(&mut w);
}
fn bar(x: &mut dyn MyTrait) {
x.some_function();
}
If the trait is not defined in your crate, and hence you cannot use an attribute macro, you can use this alternate form, but it requires copying the trait definition:
dyner::inline::adapter_struct! {
struct InlineAsyncIterator for trait MyTrait {
async fn foo(&mut self);
}
}
This section is going to dive into the details of how this proposal works within the compiler. As a user, you should not generally have to know these details unless you intend to implement your own adapter shims.
Let's repeat our running example trait, AsyncIterator
:
trait AsyncIterator {
type Item;
async fn next(&mut self) -> Option<Self::Item>;
}
async fn count(i: &mut dyn AsyncIterator) -> usize {
let mut count = 0;
while let Some(_) = i.next().await {
count += 1;
}
count
}
Recall that async fn next
desugars to a regular function that returns an impl Trait
:
trait AsyncIterator {
type Item;
fn next(&mut self) -> impl Future<Output = Option<Self::Item>>;
}
Which in turn desugars to a trait with an associated type (note: precise details here are still being debated, but this desugaring is "close enough" for our purposes here):
trait AsyncIterator {
type Item;
type next<'me>: Future<Output = Option<Self::Item>>
fn next(&mut self) -> Self::next<'_>;
}
dyn
When count
is compiled, it needs to invoke i.next()
. But the AsyncIterator
trait simply declares that next
returns some impl Future
(i.e., "some kind of future"). Each impl will define its own return type, which will be some kind of special struct that is "sufficiently large" to store all the state that needed by that particular impl.
When an async function is invoked through static dispatch, the caller knows the exact type of iterator it has, and hence knows exactly how much stack space is needed to store the resulting future. When next
is invoked through a dyn AsyncIterator
, however, we can't know the specific impl and hence can't use that strategy. Instead, what happens is that invoking next
on a dyn AsyncIterator
always yields a pointer to a future. Pointers are always the same size no matter how much memory they refer to, so this strategy doesn't require knowing the "underlying type" beneath the dyn AsyncIterator
.
Returning a pointer, of course, requires having some memory to point at, and that's where things get tricky. The easiest (and default) solution is to allocate and return a Pin<Box<dyn Future>>
, but we wish to support other kinds of pointers as well (e.g., pointers into pre-allocated stack space). We'll explain how our system works in stages, first exploring a version that hardcodes the use of Box
, and then showing how that can be generalized.
Box
Before we get into the full system, we're going to start by just explaining how a system that hardcodes Pin<Box<dyn Future>>
would work. In that case, if we had a dyn AsyncIterator
, the vtable for that async-iterator would be a struct sort of like this:
struct AsyncIteratorVtable<I> {
type_tags: usize,
drop_in_place_fn: fn(*mut ()), // function that frees the memory for this trait
next_fn: fn(&mut ()) -> Pin<Box<dyn Future<Output = Option<I> + '_>>
}
This struct has three fields:
type_tags
, which stores type information used for Any
drop_in_place_fn
, a funcdtion that drops the memory of the underlying value. This is used when the a dyn AsyncIterator
is dropped; e.g., when a Box<dyn AsyncIterator>
is dropped, it calls drop_in_place
on its contents.next_fn
, which stores the function to call when the user invokes next
. You can see that this function is declared to return a Pin<Box<dyn Future>>
.(This struct is just for explanatory purposes; if you'd like to read more details about vtable layout, see this description.)
Invoking i.next()
(where i: &mut dyn AsynIterator
) ultimately invokes the next_fn
from the vtable and hence gets back a Pin<Box<dyn Future>>
:
i.next().await
// becomes
let f: Pin<Box<dyn Future<Output = Option<I>>>> = i.next();
f.await
We've seen how count
calls a method on a dyn AsyncIterator
by loading next_fn
from the vtable, but how do we construct that vtable in the first place? Let's consider the struct YieldingRangeIterator
and its impl
of AsyncIterator
that we saw before in an earlier section:
struct YieldingRangeIterator {
start: u32,
stop: u32,
}
impl AsyncIterator for YieldingRangeIterator {
type Item = u32;
async fn next(&mut self) -> u32 {...}
}
There's a bit of a trick here. Normally, when we build the vtable for a trait, it points directly at the functions from the impl. But in this case, the function in the impl has a different return type: instead of returning a Pin<Box<dyn Future>>
, it returns some impl Future
type that could have any size. This is a problem.
To solve it, the vtable doesn't directly reference the next
fn from the impl, instead it references a "shim" function that allocates the box:
fn yielding_range_shim(
this: &mut YieldingRangeIterator,
) -> Pin<Box<dyn Future<Output = Option<u32>>>> {
Box::pin(<YieldingRangeIterator as AsyncIterator>::next(this))
}
This shim serves as an "adaptive layer" on the callee's side, converting from the impl Future
type to the Box
. More generally, we can consider the process of invoking a method through a dyn as having adaptation on both sides, like shown in this diagram:
(This diagram shows adaptation happening to the arguments too; but for this part of the design, we only need the adaptation on the return value.)
So far our design has hardcoded the use of Box
in the vtable. We can generalize this by introducing a new concept into the compiler; we currently call this a "dynx type"[2]. Like closure types, dynx types are anonymous structs introduced by the compiler. Instead of returning a Pin<Box<dyn Future>>
, the next
function will return a dynx Trait
, which represents "some kind of pointer to a dyn Trait
". Note that the dynx Trait
types are anonymous and that the dynx
syntax we are using here is for explanatory purposes only. Users still work with pointer types like &dyn Trait
, &mut dyn Trait
, etc.[3]
At runtime, a dynx Trait
struct has the same size as a Box<dyn Trait>
(two machine words). If a dynx Trait
were an ordinary struct, it might look like this:
struct dynx Trait {
data: *mut (),
vtable: *mut (),
}
Like a Box<dyn Trait>
, it carries a vtable that lets us invoke the methods from Trait
dynamically. Unlike a Box<dyn Trait>
, it does not hardcode the memory allocation strategy. Instead, a dynx vtable repurposes the "drop" function slot to mean "drop this pointer and its contents". This allows dynx Trait
types to be created from any kind of pointer, such as a Box
, &
, &mut
, Rc
, or Arc
. When a dynx Trait
struct is dropped, it invokes this drop function from its destructor:
impl Drop for dynx Trait {
fn drop(&mut self) {
let drop_fn: fn(*mut ()) = self.vtable.drop_fn;
drop_fn(self.data);
}
}
Now that we have dynx
, we can define the vtable for an AsyncIterator
almost exactly like we saw before, but using dynx Future
instead of a pinned box:
struct AsyncIteratorVtable<I> {
..., /* some stuff */
next: fn(&mut ()) -> dynx Future<Output = Option<I>>
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Look ma, no box!
}
Calling i.next()
for some i: &mut dyn AsyncIterator
will now give back a dynx
result:
i.next().await
// becomes
let f: dynx Future<Output = Option<I>> = i.next();
f.await
When the dynx f
is dropped, its destructor will call the destructor from the vtable, freeing the backing memory in whatever way is appropriate for the kind of pointer that the dynx
was constructed from.
What have we achieved? By using dynx
in the vtable, we have made it so that the caller doesn't know (or have to know) what memory management strategy is in use for the resulting trait object. It knows that it has an instance of some struct (dynx Future
, specifically) that implements Future
, is 2-words in size, and can be dropped in the usual way.
In the previous section, we saw that using dynx
in the vtable means that the caller no longer knows (or cares) what memory management strategy is in use. This section explains how the callee actually constructs the dynx
that gets returned (and how impls can tweak that construction to choose the memory management strategy they want).
Absent any other changes, the impl
of AsyncIter
contains an async fn next()
that returns an impl Future
which could have any size. Therefore, to construct a dynx Future
, we are going to need an adaptive shim, just like we used in the Pin<Box>
example. In fact, the default shim is almost exactly the same as we saw in that case. It allocates a pinned box to store the future and returns it. The main difference is that it converts this Pin<Box<T>>
into a dynx Future
before returning, rather than coercing to a Pin<Box<dyn Future>>
return type (the next section will cover shim functions that don't allocate a box):
// "pseudocode", you couldn't actually write this because there is no
// user-facing syntax for the `dynx Future` type
fn yielding_range_shim(
this: &mut YieldingRangeIterator,
) -> dynx Future<Output = Option<u32>> {
let boxed = Box::pin(<YieldingRangeIterator as AsyncIterator>::next(this));
// invoke the (inherent) `new` method that converts to a `dynx Future`
<dynx Future>::new(boxed)
}
The most interesting part of this function is the last line, which construct the dynx Future
from its new
function. Intuitively, the new
function takes one argument, which must implement the trait IntoRawPointer
(added by this design). The IntoRawPointer
trait is implemented for smart pointer types int the standard library, like Box
, Pin<Box>
, and Rc
, and represents "some kind of pointer" as well as "how to drop that pointer":
// Not pseudocode, will be added to the stdlib and implemented
// by various types, including `Box` and `Pin<Box>`.
unsafe trait IntoRawPointer: Deref {
/// Convert this pointer into a raw pointer to `Self::Target`.
///
/// This raw pointer must be valid to dereference until `drop_raw` (below) is invoked;
/// this trait is unsafe because the impl must ensure that to be true.
fn into_raw(self) -> *mut Self::Target;
/// Drops the smart pointer itself as well as the contents of the pointer.
/// For example, when `Self = Box<T>`, this will free the box as well as the
/// `T` value.
unsafe fn drop_raw(this: *mut Self::Target);
}
The <dynx Future>::new
method just takes a parameter of type impl IntoRawPointer
and invokes into_raw
to convert it into a raw pointer. This raw pointer is then packaged up, together with a modified vtable for Future
, into the dynx
structure. The modified vtable is the same as a normal Future
vtable, except that the "drop" slot is modified so that its drop function points to IntoRawPointer::drop_raw
, which will be invoked on the data pointer when the dynx
is dropped. In pseudocode, it looks like this:
// "pseudocode", you couldn't actually write this because there is no
// user-facing syntax for the `dynx Future` type; but conceptually this
// inherent function exists (in the actual implementation, it may be inlined
// by the compiler, since you could never name it
struct dynx Future<O> {
data: *mut (), // underlying data pointer
vtable: &'static FutureVtable<O>, // "modified" vtable for `Future<Output = O>` for the underlying type
}
struct FutureVtable<O> {
/// Invokes `Future::poll` on the underlying data.
///
/// Unsafe condition: Expects the output from `IntoRawPointer::into_raw`
/// which must not have already been freed.
poll_fn: unsafe fn(*mut (), cx: &mut Context<'_>) -> Ready<()>,
/// Frees the memory for the pointer to future.
///
/// Unsafe condition: Expects the output from `IntoRawPointer::into_raw`
/// which must not have already been freed.
drop_fn: unsafe fn(*mut ()),
}
impl<O> dynx Future<Output = O> {
fn new<RP>(from: RP)
where
RP: IntoRawPointer,
RP::Target: Sized, // This must be sized so that we know we have a thin pointer.
RP::Target: Future<Output = O>, // The target must implement the future trait.
RP: Unpin, // Required because `Future` has a `Pin<&mut Self>` method, see discussion later.
{
let data = IntoRawPointer::into_raw(from);
let vtable = FutureVtable<O> {
poll_fn: <RP::Target as Future>::poll,
drop_fn: |ptr| RP::drop_raw(ptr),
}; // construct vtable
dynx Future {
data, vtable
}
}
}
impl<O> Future for dynx Future<Output = O> {
type Output = O;
fn poll(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Ready<()> {
// Unsafety condition is met since...
// 1. self.data was initialized in new and is otherwise never changed.
// 2. drop must not yet have run or else self would not exist.
unsafe {
// Conceptually...
let pin: Pin<RP> = Pin::new(self); // requires `RP: Unpin`.
let pin_mut_self: Pin<&mut RP::Target> = pin.as_mut();
self.vtable.poll_fn(pin_mut_self, cx);
self = pin.into_inner(); // XXX is this quite right?
self.vtable.poll_fn(self.data, cx)
}
}
}
impl<O> Drop for dynx Future<Output = O> {
fn drop(&mut self) {
// Unsafety condition is met since...
// 1. self.data was initialized in new and is otherwise never changed.
// 2. drop must not yet have run or else self would not exist.
unsafe {
self.vtable.drop_fn(self.data);
}
}
}
In the previous section, we explained how the default "shim" created for an async fn
allocates Box
to store the future; this Box
is then converted to a dynx Future
when it is returned. Using Box
is a convenient default, but of course it's not always the right choice: for this reason, you can customize what kind of shim using an attribute, #[dyn]
, attached to the method in the impl:
#[dyn(box)]
– requests the default strategy, allocating a box#[dyn(identity)]
– requests a shim that just converts the returned future into a dynx
. The returned future must be of a suitable pointer type (more on that in the next section).An impl of AsyncIterator
that uses the default boxing strategy explicitly would look like this:
impl AsyncIterator for YieldingRangeIterator {
type Item = u32;
#[dyn(box)]
async fn next(&mut self) { /* same as above */ }
}
If we want to avoid the box, we can instead write an impl for AsyncIterator
that uses dyn(identity)
. In this case, the impl is responsible for converting the impl Future
return value into a an appropriate pointer from which a dynx
can be constructed. For example, suppose that we are ok with allocating a Box
, but we want to do it from a custom allocator. What we would like is an adapter InAllocator<I>
which adapts some I: AsyncIterator
so that its futures are boxed in a particular allocator. You would use it like this:
fn example<A: Allocator>(allocator: A) {
let mut iter = InAllocator::new(allocator, YieldingRangeIterator::new();
fn_that_takes_dyn(&mut iter);
}
fn fn_that_takes_dyn(x: &mut dyn AsyncIterator) {
// This call will go into the `InAllocator<YieldingRangeIterator>` and
// hence will allocate a box using the custom allocator `A`:
let value = x.next().await;
}
To implement InAllocator<I>
, we first define the struct itself:
struct InAllocator<A: Allocator, I: AsyncIterator> {
allocator: A,
iterator: I,
}
impl<A: Allocator, I: AsyncIterator> InAllocator<A, I> {
pub fn new(
allocator: A,
iterator: I,
) -> Self {
Self { allocator, iterator }
}
}
and then we implement AsyncIterator
for InAllocator<..>
, annotating the next
method with #[dyn(identity)]
.
The next
method
impl<A, I> AsyncIterator for InAllocator<A, I>
where
A: Allocator + Clone,
I: AsyncIterator,
{
type Item = u32;
#[dyn(identity)]
fn next(&mut self) -> Pin<Box<I::next, A>> {
let future = self.iterator.next();
Pin::from(Box::new_in(future, self.allocator.clone()))
}
}
We have presented a lot of material here, but believe it or not, it is not the complete design! There are some interesting details to come, and they become important indeed in the "feel" of the overall design. Covering those details however would make this document too long, so we're going to split it into parts. Nonetheless, for completeness, this section lists out some of those "questions yet to come".
In the previous section, we showed how a #[dyn(identity)]
function must return "something that can be converted into a dynx struct", and we showed that a case of returning a Pin<Box<impl Future, A>>
type. But what are the general rules for constructing a dynx
struct? You're asking the right question, but that's a part of the design we haven't bottomed out yet.
In short, there are two "basic" approaches we could take. One of them is more conservative, in that it doesn't change much about Rust today, but it's also much more complex, because dyn
dealing with all the "corner cases" of dyn
is kind of complicated. The other is more radical, but may result in an overall smoother, more coherent design.
Apart from that tantalizing tidbit, we are intentionally not providing the details here, because this document is long enough as it is! The next document dives into this question, along with a related question, which is how dynx
and sealed traits interact.
This is actually a complex question with (at least) two possible answers. We
We plan to address this in a follow-up RFC. The core idea is to build on the notation that one would use to express that you wish to have an async fn return a Send
. As an example, one might write AsyncIterator<next: Send>
to indicate that next()
returns a Send
future; when we generate the vtable for a dyn AsyncIterator<next: Send>
, we can ensure that the bounds for next
are applied to its return type, so that it would return a dynx Future + Send
(and not just a dynx Future
). We have also been exploring a more convenient shorthand for declaring "all the futures returned by methods in trait should be Send
", but to avoid bikeshedding we'll avoid talking more about that in this document!
dynx Trait
structs and "sealed traits" interact?As described here, every dyn-safe trait Trait
gets an "accompanying" dynx Trait
struct and an impl Trait for dynx Trait
impl for that struct. This can have some surprising interactions with unsafe code – if you have a trait that can only be safely implemented by types that meet certain criteria, the impl for a dynx
type may not meet those criteria. This can lead to undefined behavior. The question then is: whose fault is that? In other words, is it the language's fault, for adding impls you didn't expect, or the code author's fault, for not realizing those impls would be there (or perhaps for not declaring that their trait had additional safety requirements, e.g. by making the trait unsafe). This question turns out to be fairly complex, so we'll defer a detailed discussion beyond this summary. ([Details here])
A dynx Trait
type is basically an implicit struct that implements Trait
. For some traits, notably "sealed" traits, this could potentially be surprising.
Consider a trait PodTrait
that is meant to be implemented only for "plain old data" types, like u8
and u32
. By leveraging the "sealed trait" pattern, one can make it so that users cannot provide any impls of this trait:
pub mod public_api {
mod sealed {
pub trait Supertrait { }
}
/// **Guarantee.** Every type `T` that implements `PodTrait` meets
/// this condition:
///
/// * Given a `&T`, there are `byte_len` bytes of data behind the
/// pointer that safely be zeroed
pub trait PodTrait: sealed::Supertrait {
fn byte_len(&self) -> usize;
}
impl PodTrait for u8 {
fn byte_len(&self) -> usize { 1 }
}
impl PodTrait for u32 {
fn byte_len(&self) -> usize { 4 }
}
...
}
This trait could then be used to build safe abstractions that leverage unsafe reasoning:
pub mod public_api {
...
pub fn zero(pod: &mut impl PodTrait) {
let n = pod.byte_len();
// OK: We have inspected every impl of `PodTrait`
// and we know that `byte_len` accurately
// describes the length of a pointer.
unsafe {
<*mut _>::write_bytes(pod, 0, n)
}
}
...
}
Unfortunately, this reasoning is not sound if combined with a dynx
type:
pub mod public_api {
...
trait GetPodTrait {
fn get(&self) -> impl PodTrait;
}
}
fn method(x: &dyn GetPodTrait) {
// `y` will be a `dynx GetPodTrait`, which will be
// a `Box<u8>` or `Box<u32>`
let mut y = x.get();
// Calling `zero` is trying to zero the memory *referenced* by
// the box, but it will actually zero the box pointer itself.
// Bad!
public_api::zero(&mut y);
}
What went wrong here? The problem is implementing PodTrait
carries an additional proof obligation beyond those that the Rust type checker is aware of. As the comment says, one must guarantee that byte_len
, invoked on an &impl PodTrait
, correctly describes the number of bytes in that memory (and that it can safely be zeroed). This invariant was manually verified for the u8
and u32
impls, but it does not hold for the impl PodTrait for dynx PodTrait
generated by the compiler.
There are a few ways to resolve this conundrum:
PodTrait
should have been declared as unsafe
, and that -> impl Foo
is not dyn safe if Foo
is an unsafe trait.
PodTrait
as safe; if it were declared as unsafe
, and we used that to suppress the dynx
impl (after all, we can't know whether it satisfies the extra conditions), the above code would no longer compile.unafe
to be more of a tool for the user – if you have private functions and types, you are supposed to be able to reason fully about what they do without needing internal unsafe
annotations (there hasn't been a formal decision about whether this is a valid principle, but it has many adherents, and it's possible we should make it one).dynx
, which could in some cases be useful.
dyn trait Foo
instead of just having a trait Foo
that doesn't make use of various prohibited features). In that case, the user is "opting in" to the generation of the dynx
impl; e.g., a dyn trait PodTrait
declaration would be simply broken, because that dyn trait
declaration implies the generation of an impl PodTrait for dynx PodTrait
, and the safety criteria are not met (this is subtle, but then the code itself is subtle).
Trait
for Box<dyn Trait>
, &dyn Trait
, and other pointer types (perhaps all pointer types).SomeTrait
includes an -> impl Foo
method, make the trait dyn safe only if SomeTrait
could itself have declared a struct that implements Foo
SomeTrait
is outside the sealed abstraction, and so won't be able to implement it; or (b) it's your own fault, because it's inside the sealed abstraction.the pointer type P
from which a dynx Bounds
is constructed has to meet various conditions depending on the details of Bounds
:
IntoRawPointer
which ensures:
Deref
and DerefMut
are stable, side-effect free and all thatinto_raw
Bounds
includes a &mut self
method, P
must be DerefMut
Bounds
includes a &self
method, P
must be Deref
Bounds
includes Pin<&mut Self>
, P
must be Unpin
… and … something something DerefMut
? how do you get from Pin<P>
to Pin<&mut P::Target>
?Bounds
includes Pin<&Self>
, P
must be Unpin
… and … something something DerefMut
? how do you get from Pin<P>
to Pin<&mut P::Target>
?Bounds
includes an auto trait AutoTrait
, P
must implement AutoTrait
dynx Bounds
implements the auto trait AutoTrait
(in general, dynx Bounds
implements all of Bounds
)Bounds
must be "dyn safe"alternative:
Bounds
IntoRawPointer
to be able to convert from raw pointer to &Self
, &mut Self
and friendsPin
reasoning really correct?dynx Future
must be constructed from a Pin<P>
pointer
dynx Trait
needs a pinned input if Trait
has Pin<&...>
methodsArguably, yes, though obviously not with that name. A dynx Trait
is a very convenient little entity: like a dyn Trait
, it supports dynamic dispatch, but unlike a dyn Trait
, it is Sized
. This means that it goes much further towards realizing the original hope, that one could write fn foo(x: impl Debug)
and then supply foo
with a dyn Debug
. In initial versions of our proposal, we were looking at introducing dynx as a user-facing feature, with the "working keyword" of obj
(e.g., obj Debug
would replace dyn Debug
), but we decided it was overloading this proposal to try and mix that in. Instead, we focused on integrating dynx
as an "invisible" piece of the semantics, so that later we can potentially add it as a user-facing feature that replaces dyn
more generally.
This is a brief summary of the user-facing changes.
dyn Trait
to include:
-> impl Trait
(note that -> (impl Trait, impl Trait)
or other such constructions are not supported)
Trait
is dyn safeimpl
to permit
#[dyn(box)]
and #[dyn(identity)]
annotationsImplementation steps:
This Appendix demonstrates how the inline async iterator adapter works.
pub struct InlineAsyncIterator<'me, T: AsyncIterator> {
iterator: T,
future: MaybeUninit<T::next<'me>>,
}
impl<T> AsyncIterator for InlineAsyncIterator<T>
where
T: AsyncIterator,
{
type Item = T::Item;
#[dyn(identity)] // default is "identity"
fn next<'a>(&'a mut self) -> InlineFuture<'me, T::next<'me>> {
let future: T::next<'a> = MaybeUninit::new(self.iterator.next());
// Why do we need this transmute? Is 'a not sufficient?
let future: T::next<'me> = transmute(future);
self.future = future;
InlineFuture::new(self.future.assume_init_mut())
}
}
pub struct InlineFuture<'me, F>
where
F: Future
{
future: *mut F,
phantom &'me mut F
}
impl<'me, F> InlineFuture<'me, F> {
/// Unsafety condition:
///
/// `future` must remain valid for all of `'me`
pub unsafe fn new(future: *mut F) -> Self {
Self { future }
}
}
impl<'me, F> Future for InlineFuture<'me, F>
where
F: Future,
{
fn poll(self: Pin<&mut Self>, context: &mut Context) {
// Justified by the condition on `new`
unsafe { ... }
}
}
impl<F> IntoRawPointer for InlineFuture<F> {
type Target = F;
// This return value is the part that has to be thin.
fn into_raw(self) -> *mut Self::Target {
self.future
}
// This will be the drop function in the vtable.
unsafe fn drop_raw(this: *mut Self::Target) {
unsafe { drop_in_place(self.future) }
}
}
impl<'me> Drop for InlineFuture<'me, F> {
fn drop(&mut self) {
unsafe { <Self as IntoRawPointer>::drop_raw(self.future); }
}
}
Ever since I once saw Dave Herman use this bizarre latin plural, I've been in love with it. –nikomatsakis ↩︎
Obviously the name "dynx type" is not great. We were considering "object type" (with e.g. obj Trait
as the explanatory syntax) but we're not sure what to use here. ↩︎
It may make sense to use dynx
as the basis for a user-facing feature at some point, but we are not proposing that here. ↩︎