Goal planning: November 2023

Attendance: TC, eholk


Roadmap pre-planning

flowchart LR

MaybeAsync["Maybe Async (Trait Definitions)"]
MaybeAsyncInStd["Using MaybeAsync in traits in std"]
AsyncIterator["AsyncIterator"]
AsyncFnNext["async fn next"]
AsyncDrop["Async Drop"]
AsyncCancellation["Async Cancellation"]
AsyncGenerators["Async Generators"]
AsyncClosures["Async Closures"]
BecomingTeam["Becoming a Team"]
RTN["Return Type Notation"]
AFITDyn["Dyn dispatch for AFIT"]
ExecutorSpawn["Executor/Spawn traits"]
TAIT["Type alias impl Trait"]
FuturesConcurrency["futures-concurrency"]
ForAwait["for await syntax"]
AsyncSocketsAPI["Async sockets API"]
AsyncFileAPI["Async file API"]
DefaultAsyncRuntime["Default async runtime"]
AsyncMain["async fn main and #[test]"]
AsyncScopedTasks["Async scoped tasks"]
StructuredConcurrency["Structured concurrency"]
StructuredParallelism["Structured parallelism"]
Combinators2["Combinators 2.0"]
ProtocolPortability["Portability for protocol implementations"]
IouringSupport["Support for iouring"]
AsyncRead["AsyncRead, AsyncWrite, AsyncBufRead, etc."]

MaybeAsyncInStd -->|dep| MaybeAsync
AsyncIterator -->|dep| MaybeAsync
AsyncIterator -->|maybe dep| AsyncFnNext
AsyncFnNext -->|dep| TAIT
FuturesConcurrency -->|dep| AsyncIterator
AsyncCancellation -->|dep| AsyncDrop

Project board:

https://github.com/orgs/rust-lang/projects/28

Recent blog post from boats:

https://without.boats/blog/a-four-year-plan/

High-level goals

  • People can't write things they want to write
  • People have to use external crates for things that should be in std
  • Ecosystem crates can't interoperate well because we don't have foundations in std
  • Async is too hard? In what ways?
  • Portability for protocols
  • Expressiveness gaps
    • Where we define expressiveness as "the ability to express something at all", at least without going to extraordinary lengths

Big questions

Random thoughts

All Generators that are not pinned are Iterators.

All Iterators are Generators.

impl<T> Generator for T
where
    T: Iterator + Unpin // Iterator is always Unpin already
{ ... }

impl<T> Iterator for G
where
    T: Generator + Unpin
{ ... }

Can we have both of these blanket impls at once?

If we only have one path into this cycle, maybe this is OK?

Would not be allowed if there were both a hand-written impl of Iterator and Generator. But these would be more specific, so perhaps we'd just ignore the other two.

We need a way to be polymorphic over the Self type. Is there an angle here with trait aliases?

#![feature(trait_alias)]

use core::pin::Pin;

trait GeneralIterator {
    type This;
    type Item;
    fn next(x: Self::This) -> Self::Item;
}

trait Generator<'a> = GeneralIterator<This = Pin<&'a mut Self>>;
trait Iterator<'a> = GeneralIterator<This = &'a mut Self>;
#![feature(trait_alias)]

use core::pin::Pin;

trait GeneralIterator {
    type This;
    type Item;
    fn next(x: Self::This) -> Self::Item;
}

trait Generator = GeneralIterator<This = for<'a> Pin<&'a mut Self>>;
trait Iterator = GeneralIterator<This = for<'a> &'a mut Self>;

This is HKT, right?

So can we express this with GATs?

What about internal iteration for generators?

Does the coroutine transform buy us a solution there? Rather than passing in a closure, we pass in a coroutine, then resume = yield.

We'd probably need a dual coroutine transform to make a for loop into a coroutine to pass into an iterator.

We don't want to let RTN sit around forever.

An alternate to RTN, inferred associated types

trait Trait {
    type Foo;
    fn method() -> Self::Foo {
        ()
    }
    async fn foo() -> Self::Foo {}
    
    // Where RTN doesn't quite work.
    fn foo2() -> (impl Future<..>, impl Future<..>) { todo!() }
    // Instead:
    fn foo2() -> (Self::Ret1, Self::ret2) { todo!() }
}

dyn* is somewhat stalled. CE was working on making it work for async and dyn Trait. It was put on hold pending AFIT. So it's at a good spot to pick up again and start going.

Do we do the most general version that Niko proposed, or start with only the pointer-sized ones?

If our dyn* could store two or three or four pointers worth of data, that means a lot more futures could be stored inline rather than having to be allocated. That may be convincing.

Select a repo