Trait multiplication

Status

Seems unlikely to be adopted, but may be the seed of a better idea

Summary

Introduce a new form of bound, trait multiplication. One can write T: Iterator * Send and it means T: Iterator<Item: Send> + Send (using the notation from RFC 2289). More generally, Foo * Bar means Foo + Bar but also that Foo::Assoc: Bar for each associated type Assoc defined in Foo (including anonymous ones defined by async functions).

What's great about this

The cool thing about this is that it means that the check whether things are Send occurs exactly when it is needed, and not at the definition site. This makes async functions in trait behave more analogously with ordinary impls. (In contrast to implied bounds.)

With this proposal, the background logging scenario would play out differently depending on the executor style being used:

  • Thread-local: logs: impl AsyncIterator<Item = String> + 'static
  • Thread per core: logs: impl AsyncIterator<Item = String> + Send + 'static
  • Work stealing: logs: impl AsyncIterator<Item = String> * Send + 'static

The key observation here is that + Send only tells you whether the initial value (here, logs) is Send. The * Send is needed to say "and the futures resulting from this trait are Send", which is needed in work-stealing sceanrios.

What's not so great about this

Complex

The distinction between + and * is subtle but crucial. It's going to be a new thing to learn and it just makes the trait system feel that much more complex overall.

Reptitive for multiple traits

If you had a number of async traits, you would need * Send for each one:


#![allow(unused)]
fn main() {
trait Trait1 {
    async fn method1(&self);
}

trait Trait2 {
    async fn method2(&self);
}

async fn foo<T>()
where
    T: Send * (Trait1 + Trait2)
{

}
}