How often do you want non-send futures?

I hope you don't mind me adding considerations to this already long thread. What I want to talk about isn't necessarily limited to futures either.

I run into a lot of trouble designing libraries which are transparent to Send. Eg. defining behavior in traits, defining how those traits interact with each other and returning futures from trait methods. Currently as Box<dyn Future<_>> because of the lack of async trait methods right now. The user owns the types that they implement the trait for, and the user will spawn the futures, or if the library does so, it does so on a global executor defined by the application developer.

As an example take an actor model with a Message trait. Ideally: If the type implementing Message is Send, the user can send the message to an actor living in another thread. If it is !Send they can't. Another example. If the type implementing the Actor trait is Send, it's mailbox (which owns the actor) can be spawned on a threadpool, if it's !Send, only on a single threaded executor.

This means the constraints faced by the user are their own. The library is not constraining them.

It's currently AFAICT not possible to express this in Rust. All the traits and implementation code of the actor library that deal with generic parameters as A: Actor will always have to specify Sendness. The same is true for Boxed trait objects in return types.

This imposes severe design limitations. Either everything has to be Send, and it can never be used for types that aren't (even in a single threaded program), or nothing is Send, and end users can never use a threadpool for spawning futures returned by the library (notably the mailbox of their actors).

The only reasonable solution I can figure out for this problem right now is if there would be a ?Send unbound telling the compiler to automatically figure out from the underlying type if it is safe to send it across threads, when it happens. The compiler will know when the final application is being compiled if things are send across or not. Maybe there could be other ways to solve this than ?Send, but I haven't found them yet.


As a way to late answer to the title of this post. I think the whole point of async programing is that you can do parallel programming without threads. Therefor I think support for !Send futures should be first class. I use single threaded executors all the time (and with data that is !Send, most commonly Rc but could be other things), and I'm not "on fuchsia". The main reason for using single threaded executors for me is performance. In some use cases the overhead from locks in channels is the majority of the running time.

8 Likes