How to force an async function return type to be Send?

use std::rc::Rc;
use tokio::task::JoinHandle;

async fn s() -> bool {
    let a = Rc::new(());
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    true
}

#[tokio::main]
async fn main() {
    let x: JoinHandle<bool> = tokio::spawn(async { s().await });
}

In this example, if we comment out the main function, it compiles. What if I wanted to force the function ˋfˋ such that its ˋFutureˋ return type is ˋSendˋ? Imagine that ˋsˋ could be inside a trait and I didn't want it to be implemented wrongly (without Send)

The only way I know is to desugar and write ˋ-> impl Future<...> + Sendˋ. Is there a better way?

2 Likes

Send on async functions is not prescriptive, but descriptive. You can't tell the compiler to make the function Send. You must write code of the function in the way that allows it to be Send, and it will be Send whenever possible.

In your case you must avoid using Rc across await points. Keep in mind that Drop runs at the end of the scope, so let a = Rc keeps the Rc alive until the end of the function. Wrap it in a block { let a = Rc… } and it will enable the future to be Send.

1 Like

You can add a test for it like this:

async fn s() -> bool {
    let a = Rc::new(());
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    true
}

fn assert_send<T: Send>(_: T) {}

#[test]
fn s_is_send() {
    assert_send(s());
}
1 Like

what if ˋsˋ was inside a trait and I wanted implementers to remember that it should be ˋSendˋ? I just had a problem where this happened

If you are defining a trait with async functions, then the trait should be declared using impl Future + Send. There's no other way right now.

Implementors can still use async fn even if the trait is defined using impl Future + Send.

2 Likes

Probably you'd want to switch from Rc to Arc. The future is not send because it contains things that aren't send: an Rc. Arc is the thread-safe equivalent of Rc.

Just keep in mind that Arc is approximately 20-30 (twenty-thirty) times slower than Rc, but that doesn't mean that replacement of Rc with Arc would slow down everything similarly: usually Rc or Arc takes few percent of the execution time, so your timings would be something like Arc uses 2-3% of whole program execution while Rc would be 0.05-0.1%: fantastic speedup of that tiny piece of code but only 2-3% speedup of the whole program. And that's if you actively clone your Arcs! If you don't do that then it would 0.5% vs 0.01%.

Of course with such a drastic difference in speed certain types of programs benefit tremendously from having Rc available and safe in Rust… but the majority of programs would be fine with Arc and Rc both.

Indeed--I would say that someone at the level that OP seems to be at should probably mostly focus for now on mastering thread safety before trying to do that level of micro-optimizations.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.