Continuing the discussion from We hate the async fn ;-; (/lh):
I was wondering if I can clarify that my function returns a future that is Send
. This is what I came up with:
pub mod m {
async fn nop() {
}
pub fn foo() -> impl std::future::Future<Output = ()> + Send {
async move {
// When we later add this, we get a compiler error:
//struct NotSend(std::marker::PhantomData<*const ()>);
//let _not_send = NotSend(std::marker::PhantomData);
nop().await;
}
}
}
But things get more complex when the question whether the future is Send
or !Send
depends on a type argument. Consider this:
pub mod m {
async fn nop() {
}
pub async fn foo<T>(value: T) {
// We later add some more code to this function without changing its signature:
//struct NotSend(std::marker::PhantomData<*const ()>);
//let _not_send = NotSend(std::marker::PhantomData);
nop().await;
drop(value);
}
}
pub fn run() -> impl std::future::Future<Output = ()> + Send {
m::foo(())
}
Here foo
(in its current implementation) returns a Send
able future if T
is Send
. But I guess there is no way to express it in Rust? So when foo
's implementation is changed, the module m
compiles properly, but the function run
won't compile. Also
rustdoc
doesn't seem to document whether a returned future is Send
(or when it is Send
).
When I do this:
pub mod m {
async fn nop() {
}
pub fn foo<T>(value: T) -> impl std::future::Future<Output = ()> + Send
where
T: Send,
{
async move {
//struct NotSend(std::marker::PhantomData<*const ()>);
//let _not_send = NotSend(std::marker::PhantomData);
nop().await;
drop(value);
}
}
}
pub fn run() -> impl std::future::Future<Output = ()> + Send {
m::foo(())
}
Then I have to restrict T
to always be Send
, which will cause a chain-reaction of required trait-bounds in my code, having to add Send
everywhere.
Is there any nicer solution to this problem and/or what is the current state of discussion? I assume this issue also comes up when implementing async traits?