Expand description
So, you have a nice async fn and you want to store a future it returns in
a struct. There’s no need for boxing or dynamic dispatch: you statically
know the type. You just need to #[name_it].
#[name_it(Test)]
async fn add(x: i32, y: i32) -> i32 {
do_something_very_async().await;
x + y
}
let foo: Test = add(2, 3);
assert_eq!(block_on(foo), 5);Function attributes (including doc comments) are preserved. Created type will have the same visibility as the function itself and the same size and alignment as original future.
MSRV is 1.61. As far as I know, it’s impossible to make it work on older Rust versions.
Safety
I don’t see why this would be unsound. Miri likes it, I discussed it with other people, and all unsafe involved isn’t particulary criminal.
To address some particular concerns:
-
Transmuting any type to an array of
MaybeUninit<u8>and back is currently considered sound. -
Alignment of the generated type is preserved.
-
Generated type is never used unpinned, except in destructor, and is never moved in destructor.
-
Lifetime of the generated type is tied to the lifetimes of every input, so use-after-free is not possible.
Nonetheless, I can’t be completely sure that it is sound yet. If you find any soundness problems, please, file an issue.
Limitations
Absolute
-
It emulates TAITs, but it can’t emulate GATs, so async trait methods capturing
selfby ref are no-go. -
It can’t be directly applied to a method. You can move the body of the method into a free function and make your method a thin sync wrapper.
(Probably) solvable
-
It doesn’t currently support generics. There’s no fundamental problem with it, it just needs to be implemented in the macros (help wanted!). Reusing lifetimes from the outer scope (i.e. in static associated functions) is also not allowed, so all involved lifetimes must be elided.
-
All arguments must be simple identifiers (so things like
(x, y): (i32, i32)are not allowed). Again, this should be possible to implement, it’s just not implemented yet. -
All generated types are
!Unpin. It’s possible to make themUnpinif the underlying future isUnpin, but that hasn’t been done yet. -
While the underlying trick could work on most
impl Traittypes, this crate only implements it forasync fn. It’s not clear how to make the macro work for any trait.
Structs
Wrapper type for named futures. Type of your future will be something like
Attribute Macros
A way to name the return type of an async function. See crate docs for more info.