# Async Fundamentals Notes: 2022
## 2022-12-09
Unsized returns
MVP with just `.box`
What are we trying to solve with this?
* Stabilizing traits in libraries
Problems
* Can't write one `impl<T: Trait + ?Sized> Trait for Box<T>`
* Sized impl
* `dyn`
* `dyn` + `Send`
imagine we had
```rust
#[derive(BoxImpl)]
trait AsyncIter {
type Item;
async fn next(&mut self) -> Option<Self::Item>;
}
impl<T: AsyncIter> AsyncIter for Box<T> { }
impl<I> AsyncIter for Box<dyn AsyncIter<Item = I>> {
fn next(&mut self) -> Box<dyn Future<Item = I>> {
}
}
impl<I> AsyncIter for Box<dyn AsyncIter<Item = I> + Send> { }
impl<I> AsyncIter for Box<dyn AsyncIter<Item = I> + Send + Sync> { }
impl<I> AsyncIter for Box<dyn AsyncIter<Item = I> + Sync> { }
// ... for every combinationof every stable auto trait ...
```
* you can later on change this code to `impl<I: ?Sized + AsyncIter> AsyncIter for Box<I>`
* this is not true, coherence rules aren't working for you here
* *but*
* `foo(Box::new(async_iter) as Box<dyn AsyncIter>)`
* `fn foo(x: impl AsyncIter)`
* `foo(&mut async_iter)`
* `&mut dyn`
* who chooses and when
* coercion site (caller of <T: Trait> method) (one who makes the dyn)
* call site (one who uses the dyn)
* expression context (`foo.box`)
* scoped "strategy"
* trait
* type
What we learned from the AFIT blog post
https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html
* Some people do indeed spawn from generics
* https://github.com/rust-lang/rust/issues/103854
* Considerable number of people do not want to capture receiver lifetimes (or other input lifetimes) in their futures
* Defaulting to Send futures continues to come up
* (in both of the above places)
`fn foo(&self) -> impl Future -> T`
`fn foo(&self) -> async T`
Return type notation
* Next step: blog post
* If it lines up with impl landing great, but start writing
* kind of like this https://hackmd.io/@nikomatsakis/SJ2-az7sc?type=view#Bounding-with-Send-Sync-and-other-bounds
* ^ Could adapt this
* Niko's proposed outline
* You can write async fn now! And in impls!
* But wait! What if you want to spawn a task? Oh shit. How do you name this return type?
* Proposal: return type notation
* Why introduce a new notation?
* at the trait level: `async(Send)`
* we need traits (e.g., AsyncRead, AsyncIter) that work for all async users
* only some want Send bounds
* the way we solve this same dilemma elsewhere in Rust is to apply the bounds at usage site, we are trying to follow the same model
* main alternative is introduce an associated type to represent the return type
* what do we call it, it won't follow the type name conventions, or else we have to worry about converting case which only works reliably in some alphabets
* but what type / lifetime paramaeters should it take? kind of weird and not ergonomic.
* `Trait * Send`
* typeof (decltype)
* the associated type references things from the arguments, e.g., lietimes+ impl trait
* also it has no name, what should we call it?
*
## 2022-11-16
[Return type notation](/vZC-EVQST2yVpeqe6c_Vrw)
## 2022-11-15
* Updates
* AFIT blog post: Revising today/tomorrow
* async backtrace
* Working [branch](https://github.com/tmandry/rust-dbg-ext/tree/async-backtrace)
* Unclear on desired format (DWARF); multiple concerns to balance: size, interop with existing tooling, Windows
* Jack interested in extending to general [runtime reflection](https://github.com/jswrenn/deflect) but the same problems apply and maybe a more focused solution is better
* Upcoming
* Blog post on RTN
niko + compiler-errors discuss where we are at:
* static async function in trait
* feature complete but
* open bug around references to Self
* unblock cjgillot
* Function return type bounds
* tyler was going to author a blog post
* next step: author an RFC
* Dynamic dispatch
* call-site
* introduce new kind of inherent method for dyn Trait
* that returns dyn Future
* coercion-site
* dyn*
* traits to determine when it gets cast
* object safety rules can be relaxed for dyn*
* casts from T to dyn (if trait contains RPITIT/async-fn) need to be checked
* HRTB
* ??
async debugging support
* Install "spawn hook" in tokio e.g.
* Then wrap with e.g. sampling profiler
* Can also intercept waker clone etc.
* Can also save off pinned pointers to every Task
* Single library can provide this support for any executor, as long as it has a
* Sync up with Jack Wrenn/mw
* This is Jack's focus right now
* Flesh out use cases
* Decide where to focus
* Common core library for providing runtime metadata
* e.g. tracing spans
* Two mechanisms: Logging pushing out, Debugger scraping up
* Can we use one API for both?
## 2022-10-28
* Priorities for next year
* Tooling
* Jack Wrenn has a proc macro solution that's based on a linked list
* tmandry prototype
* Bug case: Deadlock caused by forgetting to drop channel sender
* Can see that tx/rx are linked because they point to the same object
* General rule: If you're waiting on a Future with an Arc and some other thread has the same Arc (or some other pointer), we'll tell you about that
* Heuristic: Can filter out objects accessible by many/all tasks
* Or special casing channels
* Or looking at Wakers
* Another: Blocked on writing to bounded channel where there's no active reader
* Use cases
* Send signal and get a trace
* (debuginfo)
* perf
* "Profile by client id" or something
* Might want data to be cheaply accessible here
* One approach is walking debuginfo to find whatever locals are hanging around
* Or linked list approach could include this
* Tracing can do this too
* Background: Tried to use tracing but ran into overhead / trouble coordinating
* tokio integration: Just after polling walk it
* Profiling copies in MIR
* Could represent MIR copies as an inlined function in source code
* Then group these together in perf traces
https://dwarfstd.org/doc/DWARF5.pdf
Section 2.5 DWARF Expressions
Next steps
* AFIT blog post
* Probably inside rust
* Focus on static AFIT + using RPITIT for Send bounds
* Follow up post: Return bounds and `#[refine]` (so you can use `+ Send` without return bounds if you know the type statically)
* Short term path is to focus on static AFIT, return position bounds, tooling
* Come back to dyn in a few weeks and write something up laying out the "essence" of the different paths
## 2022-10-24
* polish has failed
* size of futures
* contributing factors
* once something is moved, do we need to keep space for it? <-- definitely UCG
* references keep things alive forever <-- maybe UCG interactions
* doubling of parameters <-- potentially a duplicate of the first, but also separate
* early drops <-- affects both sync/async
* "do we keep a vec just so we can free it"
* temporary lifetimes <-- maybe we can shrink this
* `match <expr> { ` // temporaries in `expr` should be dropped before match begins
* comes up at AWS, Fastly performance
* not living up to Rust's promise
* tooling
* idea: intrinsic that traces a future emitted by compiler
* Fuchsia debugger team interested in "async stack traces" for at-rest tasks (what's being awaited)
* There's also a use case for "send a signal and get a state of your world from the server"
* Idea: Rust library that reads DWARF and crawls tasks/futures in your process memory.
* Has an API for reading memory so it can be called from a debugger.
* Can also run in process for live "self inspection" use case
* Lets you tell it about e.g. futures-rs combinator types and so on
* async steering meeting?
* Lots of these ideas could go there...
* time-sliced workflow
* Sort-of works, but sometimes big important things come up, and also you have to be able to predict how long things will take (difficulty: impossible) for predictability
* Try scheduling 60% of your time (in practice you will have scheduled 100%)
* Having a single deliverable you're working toward can be really clarifying
* [in each area]
* e.g. One day a week, all I'm going to do is build a library that can follow an async backtrace
## 2022-10-21
* dyn and boxing
* Having to do the (small) `.box` repeated N times may be less confusing than the confusing `Boxing::new` repeated once
* `.box` should desugar to `Box::new_in` or similar
* But we'll still have adapters
* How do we spell "dyn but impls Trait"?
* `impl Trait`
* `dyn impl Trait`
* Why we still have adapters (niko catches up)
* lowest layer: `<T: AsyncRead>`
* middle layer wants to use `&mut dyn AsyncRead`
* oh shit
* What are the ingredients to this proposal
* `.box` sugar
* `dyn AsyncTrait` does not implement `AsyncTrait` but instead returns `dyn Future` (by default)
* `dyn AsyncTrait<next() = dyn* Future>` some way to override and declare that a boxing adapter was used
* boxing adapters that work on all traits (e.g., `Boxing::new`)
* inline adapter that works on all traits that use only `&mut`?
* some future way to make your own adapter that works on all traits
* What is the MVP
* Option 1: write Boxing
* have a way to override it
* make people use that override
* Option 2: use dyn but force people to hand-write "boxing" traits for each trait
* kinda sucks for the adapters pattern
*
```rust
fn foo(x: &mut dyn AsyncRead) {
x.read().box;
}
```
```rust
fn foo(x: &mut dyn AsyncRead) {
x.read().box;
}
```
How do we write the override?
* `dyn box AsyncTrait` vs `Box<dyn box AsyncTrait>`
Use cases and constraints
* nice ergonomic calls where perf doesn't matter
* impl trait at bottom layer, dyn trait at top of layer
* cached across calls or otherwise cheaper
* bounded size (all futures < 24 bytes or whatever)
* no allocator (inline)
Trait adaptation / templates
```rust
trait DynAsyncTraitUsize = AsyncTrait {
async fn *() -> Dyn<...>
}
```
`.box` really only exists to make Rust possible to learn :-)
## 2022-10-07
* "Inside Rust" for overview and status update
* some personal blog for return type notation
* Stakeholder meeting summary
* Ergonomics initiative
* Try to do some task like "write a web server in Rust"
* Sit in on a Rust training
* Come up with a list of issues to work through
## 2022-10-03
* Squinting at the future
* where Platform: Unix
* Panic, !Panic
* Capabilities, then (scoped) contexts
* Accessibility through tooling
* Type providers
* Rust as "DSL host"
* C++ template instantiation
* dyn adapters
* "For dyn"
* If `dyn Trait` doesn't impl `Trait`...
* But you need to call `fn foo(impl Trait)`...
* Then you need an adapter, or an `impl Trait for Box<dyn Trait>`, or something
Today what you might have done, but now can't, is
```rust!
impl<T: ?Sized + Trait> Trait for Box<T> {
async fn method(&mut self) {
T::method(self).await
}
}
```
but you can do
```rust
impl<T: Trait> Trait for Box<T> {
async fn method(&mut self) {
T::method(self)
}
}
// what about `Box<dyn Trait + Send>`
impl Trait for Box<dyn Trait> {
async fn method(&mut self) {
(*self).method().box.await
}
}
```
Problems:
* For clients, what about `Box<dyn Trait + Send>` and friends?
* For libraries, you don't want to make this call on their behalf
* define a "wrapper type"
inline adapter in this world
```rust!
struct InlineTrait<T: Trait> {
t: T,
space_for_future: ...,
}
struct InlineFuture {
datA: *mut (),
poll_fn: fn(),
}
impl Trait for InlineTrait {
//#[dyn(inline)]
#[refine]
fn method(&mut self) -> InlineFuture<'_> {
...
}
}
```
`dyn Trait<method() = DynFuture>`
`dyn* Trait`
```rust
fn caller(x: impl Trait) {
with DynAlloc<Trait> = InlineTraitAdapter::new(x) {
callee(&mut obj);
}
}
fn callee(x: &mut dyn Trait)
with DynAlloc<Trait>
{
x.method()
}
```
```rust
fn caller(x: impl Trait) {
with DynAlloc<_> = InlineTraitAdapter::new(x) {
callee(&mut obj);
}
}
fn callee<exists T: Trait>(x: &mut dyn T)
with DynAlloc<T>
{
x.method()
}
```
```rust
trait Trait {
async fn foo();
}
trait BoxedTrait {
fn foo() -> Dyn<Future>;
}
/*
impl BoxedTrait for dyn Trait
where
Platform: Boxing,
{
fn foo() -> Dyn<Future> {
self.foo().box
}
}
*/
impl BoxedTrait for Boxing<dyn Trait>
{
fn foo() -> Dyn<Future> {
self.foo().box
}
}
impl<X> BoxedTrait for dyn Trait<foo() = X>
where
X: IntoDynStar,
{
fn foo() -> Dyn<Future> {
self.foo().into_dyn()
}
}
fn callee(x: &mut dyn BoxedTrait){ }
```
(tmandry) What if we accept that we need `.box`, and `dyn Trait` doesn't impl `Trait`, but we also give you `Boxing` to get out of those situations where you have `dyn Trait` and need `impl Trait`?
* Simple (common?) case remains easy to explain: `foo.method().box.await`
* `.box` helps explain exactly what `Boxing` itself does!
* Retain full generality (including inline adapters) and choice of whether to decide in caller or callee
* Implication: Library code that wants to be general over allocation strategies probably doesn't use dyn
* But maybe that's okay
* As long as `Boxing::new` forgets the type it was created from
* Can't share monomorphizations across allocation strategies (like dyn*) `Boxing::new` returns something more generic, like `dyn* Trait` (what would we actually call this without being confusing? `DynAlloc<Trait>`? see below)
* But sharing monomorphizations like this probably isn't that important anyway; you'll probably only have ~one per trait per binary.
* What is important (for compile times) is not making you wait to do your monomorphizations for every trait in the binary crate.
A possible extension is `dyn Trait<method(): AtMostSize<N>>`, making `method` dispatchable.
* `DynAlloc<trait T>`
* `DynCallable<trait T>`
* `DynDispatchable<trait T>`
* `Erased<trait T>`
* `DynComplete<trait T>`
## 2022-09-22
* Static async functions in traits edge cases - https://hackmd.io/oz-pwQhNRPa3gFykJkGdpA
* Question for Niko to chase down -- can we use this to stabilize AWS ?
* Dyn async functons in traits
* Async drop
* https://sabrinajewson.org/blog/async-drop
* https://hackmd.io/DBC2RvHXT_GQqdyRRI8geQ?view
* Why do we care about explicit await points – to mark anytime stuff can happen concurrently, or only to mark cancellation points?
* Post seems to be written under the assumption that it's only cancellation
* How far can we get with `#[no_implicit_drop]` lint and `defer`?
* The lint requires interprocedural analysis, or labelling functions with effects, or monomorphization time errors/lints in those functions
* (If the generic function is in another crate we'd probably fault the caller – so maybe this isn't as bad as other monomorphization time errors? So then you'd put the `#[allow]` on that call?)
* `defer` can be async and can keep drops sync – sidesteps the problem of how to await during panic
* Though in theory you could catch_unwind and stash the panic somewhere?
* (Related: In a recent reading club meeting we discussed poisoning for async tasks that were cancelled without the ability to clean up)
## 2022-09-19
* we loosened this requirement
* `T: Trait` => can make a `dyn Trait`
* but to support call-site dispatch, we have to loosen a different requirement
* `dyn Trait` does not (necessarily) implement `Trait`
* but you can still call its methods
* so dyn dispatch is "its own thing"
* this has pros/cons
* e.g. we can have `dyn Foo` types for any trait (?) but some of the members may be inaccessible
* they are effectively inherent methods on the dyn type with a different signature than what you would get on the trait
* today
* when you call a method on a dyn type-- typeck treat it as inherent for priority but then
* it gets compiled to a trait method call with Self = dyn
* there is a magic "dyn dispatch impl" that the compiler generates
* under this proposal
* the trait methods become inherent methods but...
* `-> impl Trait` becomes `-> dyn Trait`
* `async fn` becomes `-> dyn Future`
* when you call a method on a dyn type, it gets comiled to a new MIR instruction (`CallDyn`)
* when you call something with `?Sized` return type, we supply allocator as an ABI argument
* typeck: when a call occurs in a position that doesn't supply an allocator, require `Sized`
* modify the `FnOnce` trait which currently has `type Output: Sized`
* it's niko's belief that we can get away with that, but we need to test it
* because the syntax is unstable
* for those traits which are dyn-safe, we *also* make a magic dyn dispatch impl
* which simply uses CallDyn for each method
* (plausibly, we could permit users to provide their own dyn dispatch impls down the line)
```rust
fn foo<F, R: ?Sized>(f: F)
where
F: Fn() -> R, // BUG: this is accepted today
{
// ..but, this normalizes to R, and because trait solver
// isn't smart enough, we give errors that R: Sized is not known
let x : F::Output = f(); // F::Output normalizes to R, R is sized
}
```
What is a context that does supply an allocator?
* Answer:
* return value -- e.g., `Box::new_with(|| foo())`, the call to `foo()` is return of the closure
* if we added a `box` operator, e.g. `foo().box`
* `&`, if we wanted alloca, e.g., `&foo()` -- does this work? No, not in the general case.
* options: alloca, then memcpy
* but if we had a "static enough" size, you could alloca up front, much better
* to implement `new_with`, we need some instrinsic like `call_with_context(foo, BoxAllocatorContext)`
can compile compute size?
```rust
fn foo() -> [u32] {
[0; 5]
}
fn baR() -> [u32] {
[0; 6]
}
fn baz() -> [u32] {
if true { foo() } else { bar() }
}
```
Niko's rule was:
* All user-written and declared functions have to have a sized return type
* But the compiler can generate functions with `-> dyn` return type
* this ensures that the size of the value to be returned can be stored in the vtable
* permits a simpler ABI and side-steps a lot of annoying problems
* this is not observable by end-users and could be lifted later, if we wish
But how does it get ergonomic? You need `.box` for sure.
```rust
trait AsyncIterator {
type Item;
async fn next() -> Option<Self::Item>;
}
fn foo(x: &mut dyn AsyncIterator) {
while let Some(a) = x.next().box.await {
}
}
struct DynAsyncIterator {
p: Box<dyn AsyncIterator>
}
impl AsyncIterator for DynAsyncIterator {
async fn next(&mut self) -> Option<Self::Item> {
self.p.
}
}
fn foo(x: &mut dyn AsyncIterator) {
while let Some(a) = x.next().box.await {
}
}
```
Does this permit dyn dispatch that is stack allocated?
Niko thinks no:
* To do that, you'd have to either (a) use alloca -- which doesn't fit with async Rust
* Or (b) supply a pointer into stack *somehow*, but we don't want to commit to the ABI details so we can't let you supply pointers or other things
Tyler's hybrid plan:
* we supply the allocator, but callee is not obligated to use it
* won't work simpler ABI Niko was proposing
* doesn't work because the caller has to do the deallocating, and they have to know what you did
Eric Holk's plan:
* don't really see how that fits in here but it could work with sealed traits
```rust
let x = [0; 1024].box; // could also be a postfix macro
```
What are we left with?
* Introducing a `.box` operator:
* new bikeshed, but it's general purpose and something we've wanted for a while
* could begin with postfix macro to gain experience
* Call-site becomes less ergonomic, more verbose
* Cannot use `dyn Trait` to do stack-allocated dynamic dispatch
* have to use a "shadow trait" approach to build a dyn* by hand
* *but* the inline adapter was also pretty hokey
* Overspecifying at the call site
* but it's easier to see how to gain control
* How weird is it to make `.box` be "optionally box"?
* compiler's optimize away allocations sometimes...
* but if `foo.box` yields a `Box<dyn Foo>`, you don't really have that option
* BUT if the value doesn't escape, compiler can in principle optimize the box away
How do you, e.g., use `dyn AsyncRead` in a no-std scenario? You just can't.
But you could have more than one variant of dyn that picks the strategy.
Default one pushes choice to call site, in future we might have more options, but that's a wide-open design space.
## 2022-09-15
```rust
struct Dyn<trait T, const N: usize = 2> { data: [usize; N]/* something */ }
// in my library
struct DynN<trait T, const N: usize> { data: [usize; N]/* something */ }
```
* Niko likes the idea of a richer ABI that (for example)
* allocates some stack scratch space
* this would allow e.g. `&self` and `&mut self` to both use stack sometimes (but no guarantees)
* compiler could figure out how much space based on magic analyses
* e.g., interprocedural dataflow to see how big futures are
* but this doesn't address the core question in any way
* Where can we make the decision
* coercion site
* feels right but hard to explain
* call site with some kind of scoping thing
* but how do you write code that works for callers with different needs? (e.g., no-std etc)
* and what is this scoping thing
* Dialects?
* We have them
* Nice if code compiles implies it has same semantics
* Dario:
* wanted to port a function to async that accepts an impl FnOnce
```rust
fn foo(
x: for<'a> exists<F> impl FnMut(&'a mut Self) -> F
)
```
## 2022-09-12
[Async Stakeholders Sep 2022: Notes](https://hackmd.io/RjycNB6wQbi3go5TSg-OIw)
* Add / focus questions
* Should we send out a questionnaire or agenda?
Notes
* Kinds of adapters
* Inline
* Arena (or other custom allocator)
* Cached Box
*
## 2022-08-29
```swift
actor Foo {
var sum = 0
var txns = []
func makeTransaction(txn: Transaction) {
txns.append(txn)
sum += txn.value
}
func doStuff() {
makeTransaction(...)
// If scheduleCallback takes a closure
// that is NOT @Sendable, you can just
// do this without an await:
scheduleCallback {
makeTransaction(...)
}
}
func doAsyncStuff() async {
// can await here...
}
}
// later on...
// `await` to schedule on the actor queue
await foo.makeTransaction(...)
```
```rust
let scope = moro::async_scope!(|s| {
let r1 = s.spawn(|| {
...
}).or_cancel(s);
let value: u32 = 44;
s.terminate(value).await;
22
}).await;
fn or_cancel<R, E>(
f: impl Future<Output = Result<R, E>>,
s: Scope<Reuslt<_, E>>)
-> impl Future<Output = R>
{
s.spawn(async {
match f.await {
Ok(v) => v,
Err(e) => s.terminate(Err(e)).await,
})
})
}
```
### frustrations
* Feels like it's hard to land a big, sweeping vision
* Everything feels incremental but doesn't feel like we're working from a coherent vision
* Solving async fns in traits is kind of the first step...
* What we need agreement on
* here are some use cases we are going to concentrate on
* mini-redis: lightweight server and client
* xxx
* embedded, no O/S style thing
* or...not?
* some tenets
*
## 2022-08-22
* Merge https://github.com/rust-lang/rfcs/pull/3245
* Niko starting to see an implementation path
* Goal 1: Support `async fn` in traits/impls only, and they cannot return `-> impl Trait`
* Step A: Add Return Type Notation (RTNs) as a variant of associated types
* New DefId that represents method and the substs are the method type parameters + argument types
* Normalization code knows how to unify those things and yield the declared return type
* Like other associated types, non-normalized RTN would have "bounds" from trait, but...
* for sync fns(all we have so far), the only bound is `Sized` (because we cannot return unsized values)
* Step B: Some light desugaring of traits and impls in AST->HIR lowering
* For traits:
* Permit async fn in trait
* when you ask for `fn_sig` of an async fn it is rewritten to return the RTN and not the "real return type"
* extend "bounds" for RTN such that for async fns, the other bound would be `Future<Output = ...>`.
* For impls:
* Desugar `async fn` to a `fn foo() -> NewOpaqueType` where `type NewOpaqueType: Future<Output = ...>` is a newly introduced opaque type, conceptually a sibling to the impl
* Goal 2: Support `-> impl Trait` RPITIT more generally
* Step A: Introduce RATs in the trait (a synthetic GAT for each RPITIT that appears in a fn)
* Step B: Allow normalizing RTN even when impl is not known
* Caveat: have to decide how to deal with `-> &Foo`, which is "refinable" potentially e.g. to `-> &'static Foo`
* interesting lang design problem :)
Why do we want to normalize w/o knowing the impl?
```rust
trait MyTrait {
fn properties(&self) -> (impl Trait1, impl Trait2);
}
fn foo<T: MyTrait>(&self, v: <T as MyTrait>::properties()) {
let (a, b) = v; // error, cannot normalize
// imagine that the type of something is known
let (a, b) = something.properites(); // if we can resolve something to an impl, we might get a different type, assuming we took the RFC seriously :)
}
```
Interesting questions---
If we normalize RTN, even when impl is known, we are committing to "revealing" those details, which affects Goal 2, and requires us to address the question of how to deal with multiple valid normalizations. Not the end of the world, but a fact.
A specific example: do we expect auto trait "leakage" through a trait? (No). When impl is known? :thinking_face: hmm.
## 2022-08-18
* key feature from the design that tmandry and I have not socialized
* updates to the repo that would be nice
* bringing in the doc
* stakeholders meeting
* https://hackmd.io/9AH8Zr9ESMmur0n6Nix96w?view
* AFIT
* not convinced we need a particular level of organization
* weekly or biweekly meeting
## 2022-06-30
### Async drop
Narrative:
* If you want to write cancel-safe code, you need destructors
* But destructors can't do async operations, so can't do async cleanup
* spawn a task:
* but that introduces race conditions into your code, not everything is Send or 'static, and it's generally kinda limited
* run `block_on`
* which is horrible and causes other problems
* one option:
* add a `cancel` method to futures that is async
* Niko's contention:
* cancellation in async is caused by select/race and any combinator that drops part of a task but not all of it
* we use select for two things
* one is "give me the first event" -- I believe we can handle this in a reasonable way
* the other is "share an &mut across several tasks" -- that one is harder
* Potential thing
* adapting the sync functon, embed awaits
* run to the next await -- this is not crazy
Async overloading: Can we do the thing where you write regular straightline sync code and adapt it to async? It introduces new cancellation points. Maybe only for async drop?
If we had "non-cancelable" futures, we could adapt regular straightline sync code..?
### Document
* You can write a traits/impls with async fn in it (huzzah)
* You can also write `-> impl Future` (and other impl traits)
* You can mix/match or use concrete types (if you want)
* You can use it with dyn but you have to do write
* `Boxing::new(foo)`
* if boxing is not ok, there are ways to manage, but it's out of scope for this document
* What if you want to talk about the returned future?
* Example: spawning thing
* You wrote `Foo::method(..): Send`
* this means: no matter what argument types you give to `method`, it will return a `Send` value
* Maybe a few more examples
* This works for any method in the trait (including `-> impl Foo`)
* What you if want all methods to be send?
* `#[send_alias]` -- need a better name -- exports
* `type DynFoo = Pin<Box<dyn Foo<method(..): Send> + Send>>`
* Future directions: star-shaped holes
## 2022-06-27
* Write an overview of async fn in traits
* try to read it locally
* how much to cover?
* main degrees of freedom
* `Boxing::new`
* RPITIT notation
* intersection of dyn and send bounds
* Not great aspects
* today, return type is always send
* can solve if you can write `-> impl Future` on either side
*
```rust
trait Foo {
fn get(&self) -> impl Future<Output = ()> + '_ + Send;
}
trait Foo {
// maybe a good idea but undermotivated by this
// example:
where Self::get(): Send;
async fn get(&self);
}
```
Requires us to allow:
* async sugar at impl level + `-> impl Future` at trait level
### adapters, where do they go?
```mermaid
flowchart LR
Rc --> RcData
StackFrame --> Rc
```
```mermaid
flowchart LR
Inline --> InlineData
StackFrame --> Inline
```
```rust
fn boxing<T>(t: T) -> U
where
T: CoerceUnsized<U>
{
}
trait Unsized {
const REQUIRES_BOXING: bool = false;
}
```
```rust
fn foo(reader: impl AsyncRead) {
let inline: InlineAdapter<impl AsyncRead> = InlineAdatper::new(reader); // <-- had to introduce here
let dyn: &mut dyn AsyncRead = &mut inline;
// this works beacuse
// InlineAdapter<_>: Unsize<> alwaysbecause
// InlineAdapter always returns pointers
}
fn bar(mut reader: impl AsyncRead) {
let pointer: &mut impl AsyncRead = &mut reader;
// this doesn't work because it needs to return a pointer
// but it also needs to allocate a temporary (with extra state) to point at
//
// and annoyingly rust doesn't support this :)
// stupid C ABI lacks arenas
let dyn: &mut dyn AsyncRead = InlineAdapter::new(pointer);
}
fn baz(mut reader: impl AsyncRead) {
let pointer: &mut impl AsyncRead = &mut reader;
let dyn: &mut dyn AsyncRead = Boxing::new(pointer);
// what other adapters can work this way?
}
```
## 2022-06-23
```rust
async fn read_stuff(mut x: impl AsyncRead)
{
let x = x.dyn<Box>;
foo(x).await
bar(x).await
baz(x).await
}
async fn foo(method: &mut dyn AsyncRead)
```
Niko's concern: if you are teaching, now you have to explain not only `dyn` but also `dyn<Box>`.
Josh: Why do you need dyn in this particular teaching example? What would it take to turn that into impl? Can teaching examples dodge `dyn<Box>` for a while by dodging `dyn AsyncRead` for a while?
Niko: other concern is that this is ergonomically sucky
```rust=
fn foo(x: &mut dyn FnMut())
foo(&mut ||)
fn foo(x: impl FnMut()) { foo1(&mut x) }
fn foo1(x: &mut dyn FnMut())
// polymorphize optimization
fn foo<T: FnMut>(x: T)
```
```rust
fn foo<T: Display>(t: &mut T) {
bar(t.dyn)
}
fn bar(u: &mut dyn Display) { }
```
```rust
trait DynAdapt<T, trait R> {
fn wrap(T) -> impl dyn R;
}
```
Reasons to use dyn:
* Avoid monomorphization
* Avoid making your type generic
```rust
fn as_dyn(x: impl Foo) -> Box<dyn Foo> {
Box::new(x) // okay, because box is mentioned
}
// Can we conventionally let you do this as
my_foo.into_dyn()
```
Could we spell that:
```
my_foo.box.into()
```
```rust
fn main() {
let b: Box<u32> = Box::new(22);
foo(b)
}
fn foo(x: Box<dyn std::fmt::Debug>) {
}
```
Josh has no objections to this.
Josh places an advance bet that some people will regularly go "that's really magic".
Questions:
* when/where are people using dyn
* work through "conditionally dyn safe" in detail
* note that if we have a "box this only if we have to" could be workable here
*
## 2022-06-20
### Idea: a shorter path for async fn in trait
Sketch:
* generate a "HIR" trait item that inherits from the method
* from generics POV, parent-of will be the method
* we'll have to tweak some logic around that
* potential unstable feature gates:
* naming it in a where-clause..?
* specifying it in an impl..?
```rust
// basic pattern
trait AsyncRead {
async fn read(&mut self);
}
impl AsyncRead for MyType {
async fn read(&mut self);
}
// basic desugared
trait AsyncRead {
type read: Future<Output = ()> + 'a; // inherits (in HIR) generics from `read`
fn read<'a>(&'a mut self) -> Self::read<...>; // inherit the parameters
}
impl AsyncRead for MyType {
type read = impl Future<Output = ()> + 'a; // inherits (in HIR) generics from `read`
fn read<'a>(&'a mut self) -> Self::read<...> {
...
}
}
// named it in where-clause
fn gimme_read<AR: AsyncRead>()
where
for<'a> AR::read<'a>: Send,
{
}
async fn foo(x: &u8)
async fn foo<'_>(x: &'_ u8)
->
/*impl Trait*/ #[lang = "from_generator"](move |mut _task_context|
{ let x = x; { let _t = { }; _t } })
```
Requirements:
* use async fn syntax in both trait and impl
* why?
*
* no way to "name" the resulting GAT
* why?
* don't want to specify the name of the GAT
* don't want to specify which lifetime arguments it takes
*
```rust
```
Reference scenarios:
https://rust-lang.github.io/async-fundamentals-initiative/evaluation/scenarios/async-fn-in-traits.html
Tower
```rust
pub trait Service<Request> {
type Response;
type Error;
type Future: Future
where
<Self::Future as Future>::Output == Result<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
cx: &mut Context<'_>
) -> Poll<Result<(), Self::Error>>;
fn call(&mut self, req: Request) -> Self::Future;
}
pub trait Service<Request> {
type Response;
type Error;
async fn ready(&mut self) -> Result<(), Self::Error>;
fn call(&mut self, req: Request) -> impl Future<Output = Result<Self::Response, Self::Error>>;
}
impl<R> Service<R> {
type Response;
type Error;
type Future: Future<Output = Result<Self::Response, Self::Error>>;
async fn ready(&mut self) -> Result<(), Self::Error>;
fn call(&mut self, req: Request) -> Self::Future;
}
```
AWS Rust SDK -- how they are doing their traits
```rust
pub trait ProvideCredentials: Send + Sync + Debug {
fn provide_credentials<'a>(&'a self) -> ProvideCredentials<'a>;
}
```
Can we ship a proc macro that will "desugar eventually" into something stable?
```rust
#[rust_lang::async_trait]
trait ProvideCredentials: Send + Sync + Debug {
fn provide_credentials<'a>(&'a self) -> ProvideCredentials<'a>;
}
```
```rust
let x = something.async_method();
x.poll(); // error: x is not pinned
```
```rust
trait Foo {
type Dyn: Future<Output = ()> + ?Sized;
type Fut = Box<Self::Dyn>;
fn fut(&self) -> Self::Fut;
}
#[async_trait]
trait Foo {
async fn fut(&self);
}
#[async_trait(Send)]
impl Foo for Bar {
async fn fut(&self) {
...
}
}
impl Foo for Bar {
type Dyn = dyn Future<Output = ()> + Send;
type Fut = Box<Self::Dyn>;
fn fut(&self) -> Self::Fut {
...
}
}
```
## 2022-06-16
### static async fn in trait
```rust
trait Foo {
fn method(x: impl Debug + Clone);
}
trait Bar: Foo {
#[refine]
fn method(x: impl Debug);
}
// but this is not an alias, it's a new trait
// would need some notation like this
Foo where {
#[refine]
fn method(x: impl Debug);
}
// (hearkening to this)
trait Bar = Send;
```
```
Bound = TraitName<Parameters>
| keyword Bound
| Bound where { TraitItems }
```
or
```
where T::foo: Fn(&mut T, impl Debug)
```
## 2022-06-13
Alternatives for how to handle RPITIT allocation: https://hackmd.io/duZJhaX6Qya8_J3x-Hjugg
Either...
* Explicit `dyn` coercion always requires an adapter somehow, or...
* We have to have *some* "default" way to turn N bytes into a pointer
* doesn't have to be box, but does have to be something, e.g. ada has a shadow stack, but that's a pretty major
* would be cool if user could pick what this was, eg via `type Foo: Bar` dependency injection
If we do require an adaptation strategy...
* What does it do?
* If we are adding extra parameters etc, isn't that better managed with capabilities
```rust
fn foo() {
bar(dyn &22);
}
fn bar(x: &dyn Debug)
```
```rust
fn foo() {
bar(Dyn::new(&22));
}
fn bar(x: Dyn<Debug>)
```
could we make it `&dyn` at call site?
what about other situations, like...
```rust
fn foo() {
bar(dyn Box::new(22)) // dyn as prefix keyword is wrong
}
fn bar(x: Box<dyn Debug>)
```
```rust
fn foo() {
bar(Box::new(22).dyn)
}
fn bar(x: Box<dyn Debug>)
```
can permit auto-ref:
```rust
fn foo() {
bar(22.dyn)
}
fn bar(x: &dyn Debug)
```
```rust
fn bar() {
22.dyn
22.box.dyn
}
```
## 2022-06-09
### The allocation question
Questions: who are other Josh-like stakeholders
linux kernel devs?
embedded devs?
### Ergonomic hurdles if you do mind allocating
* Knowing you need a wrapper in the first place
* Selecting the strategy you want
* You need a custom wrapper type per trait
* have to find the right one in dyner
* or else use the macros to define your own
* The syntax is "ok" `&mut Wrapper::new(x)`
### Post-meeting notes from tmandry/Josh
https://hackmd.io/ChZFnWwsRSOD0lIz-vOSHw?view
Some highlights:
* warn by default better than allow
* correctness issues (e.g., when it would be wrong to allocate) are more important than performance (though he cares about both)
* examples...
* signal handlers
* right now it's difficult but plausible to audit code for allocations in these situations
* question: minimal capabilities
* dynamic scope lint
* if you allocate, signal
* if you invoke a fn, that fn must have dynamic scope lint set to same level as the current source, or else signal
* except for traits, virtual dispatch etc
* currently rust metadata doesn't record lint levels so... that's dubious
* linting effect system
* `#[doesn_t(allocate, panic, whatever)]`
* `#[warn(allocates)]`
* signals when you call a thing without the `#[doesn_t(...)]` attribute
* possible effects:
* allocate
* panic
* io
* blocking
```rust
#[no(panic, alloc)]
fn signal_handler<T: Debug>() {
}
fn main() {
signal_handler::<SomeType>(); // warn
signal_handler::<SomeKernelType>(); // allow
}
impl Debug for SomeType { }
#[no(panic, alloc)]
impl Debug for SomeKernelType { }
```
### [ergonomics blog post](https://blog.rust-lang.org/2017/03/02/lang-ergonomics.html)
> There are three dimensions of the reasoning footprint for implicitness:
>
> Applicability. Where are you allowed to elide implied information? Is there any heads-up that this might be happening?
>
> Power. What influence does the elided information have? Can it radically change program behavior or its types?
>
> Context-dependence. How much of do you have to know about the rest of the code to know what is being implied, i.e. how elided details will be filled in? Is there always a clear place to look?
>
> The basic thesis of this post is that implicit features should balance these three dimensions. If a feature is large in one of the dimensions, it's best to strongly limit it in the other two.
Applicability. Every expression, so kinda high
Power: Typically very low, but in some contexts very high. This is why `#[no]` is interesting, because it lets us know (no pun intended) which context we are in.
Context-dependence: From the point of coercion you can find all relevant code without too much difficult (e.g., look at callee), but you must know Rust well. Rust-analyzer could highlight it for you. Low to medium?
### why not just write dyn
implication: if we want it to be a hard error to create a `dyn Foo` from a `T` where calls to the resulting methods may allocate (but statics calls through `T` would not have done)... then we have to drop the rule that:
`T: Trait`
`Trait` dyn safe
------------------------
"I can make a `dyn Trait` from `T`"
(or else, we have to require some "opt-in" when making a `dyn Trait` from a generic type `T`)
```rust
trait AsyncIter {
async fn next(&mut self);
}
fn foo<T: AsyncIter>(t: &mut T) {
bar(t)
}
fn bar(t: &mut dyn AsyncIter) {
}
```
```rust
trait AsyncIter {
async fn next(&mut self);
}
fn foo<T: dyn AsyncIter>(t: &mut T) {
bar(t)
}
fn bar(t: &mut dyn AsyncIter) {
}
```
```rust
trait AsyncIter {
async fn next(&mut self);
}
fn foo<T: dyn AsyncIter>(t: &mut T) {
bar(Dyn::new(t))
}
fn bar(t: Dyn<AsyncIter>) {
}
```
### pitch
`no` is better than status quo! Don't have to be careful about allocating anymore.
### plan
* Write a hackmd/blog draft that explains in light detail
* https://hackmd.io/-iLHMGtNTuuJ6C8WUt6T_w
* Run it by Josh
* Publish somewhere
## 2022-05-12
### Stakeholder meeting topics
* Autoboxing
* What's your experience with dyn Trait?
* Weird workarounds?
* Avoid using it altogether?
* What features do you use:
* [ ] by-value self (`fn foo(self...)`)
* [ ] impl Trait in arg position/generic fns
* if so, which traits
* [ ] impl Trait in return position
* if so, which traits
* [ ] upcasting `dyn Trait`
* [ ] clone-able trait objects
* `impl Clone for Box<dyn Trait>` (requires adding a method to trait and implementing it manually everywhere, annoying)
* Bounding by `Send`?
## 2022-05-05
### AWS Rust SDK
[example](https://docs.rs/aws-types/0.10.1/aws_types/credentials/trait.ProvideCredentials.html)
```rust
pub trait ProvideCredentials: Send + Sync + Debug {
fn provide_credentials<'a>(&'a self) -> ProvideCredentials<'a>
where
Self: 'a;
}
```
* Essentially `ProvideCredentials` is `Box<dyn Future<Output = Credentials>>`
* To implement you do
```rust
impl ProvideCredentials for MyCredentialProvider {
fn provide_credentials<'a>(&'a self) -> ProvideCredentials<'a>
where
Self: 'a
{
ProvideCredentials::new(async { ... })
}
}
```
...with async fns in traits, could change to this...
```rust
pub trait ProvideCredentials: Send + Sync + Debug {
async fn provide_credentials<'a>(&'a self) -> Credentials;
}
```
...in which case, given the refinement approach, we might still accept this...
```rust
impl ProvideCredentials for MyCredentialProvider {
fn provide_credentials<'a>(&'a self) -> ProvideCredentials<'a>
where
Self: 'a
{
ProvideCredentials::new(async { ... })
}
}
```
...but we infer that the `type provide_credentials<'bound> = ProvideCredentials<'bound>`, yay
almost entirely backwards compatible but for
```rust
fn foo<T: ProvideCredentials>() {
let x: ProvideCredentials<'_> = t.provide_credentials();
// type checks today, but shouldn't
}
```
## 2022-04-18?
### Refinement RFC
* Maybe `@override` etc is useful because it tells you when you _didn't_ override and were just overloading ( a problem we don't have in Rust); mention this in the RFC
* Will include next-edition behavior in open questions, even if we "have an opinion" in the RFC
* Ideally we implement next-edition behavior now
### Stakeholder meeting topics
* Autoboxing
* What's your experience with dyn Trait?
* Weird workarounds?
* Avoid using it altogether?
* What features do you use:
* [ ] by-value self (`fn foo(self...)`)
* [ ] impl Trait in arg position/generic fns
* if so, which traits
* [ ] impl Trait in return position
* if so, which traits
* [ ] upcasting `dyn Trait`
* [ ] clone-able trait objects
* `impl Clone for Box<dyn Trait>` (requires adding a method to trait and implementing it manually everywhere, annoying)
### Problems with `dyn*`
* `&[mut] self` methods require your `dyn*` to be in memory (so you can pass a ref to it) instead of just passing in registers
* Can we use `IntoSized` to get around this?
* Can tag impl on pointer type as `#[unsafe::transparent]`
* Compiler can invoke `IntoSized::from_raw`, see that it's a transparent impl, and therefore it can "call `Deref`" and invoke the trait directly
* Ideally for e.g. `Box<T>`, our vtable points directly to `<T as Trait>::foo`
* ABI can be take `Self::Raw` as value and return by value.. but that conflicts with the above point
* How would we coerce `Box<dyn Trait>` to `dyn* Trait`
* Or `cell::Ref<dyn Trait>` – need to store info about the layout of the original type somewhere e.g. in the vtable so we can recover the RefCell pointer from the contained value pointer
* Pointer vs pointee variance
* `&'a mut dyn Debug + 'b`
### Clean up topic backlog
## 2022-03-25
### Pin and "opaque pointer to dyn"
[How to build a dynx](https://rust-lang.github.io/async-fundamentals-initiative/explainer/async_fn_in_dyn_trait/generalizing_from_box_to_dynx.html#the-shim-function-builds-the-dynx)
```rust
unsafe trait IntoRawPointer {
/// Convert this pointer into a raw pointer to `Self::Target`.
///
/// This raw pointer must be valid to dereference until `drop_raw` (below) is invoked;
/// this trait is unsafe because the impl must ensure that to be true.
fn into_raw(self) -> *mut ();
unsafe fn from_raw(this: *mut ()) -> Self;
// maybe we will want these..
unsafe fn from_raw_ref(this: &*mut ()) -> &Self;
}
```
```rust
struct dynx Future<O> {
data: *mut (), // underlying data pointer
vtable: &'static FutureVtable<O>, // "modified" vtable for `P<Future<Output = O>>`, where `P` is the "pointer type" (e.g., `Pin<Box<...>>`)
}
struct FutureVtable<O> {
/// Invokes `Future::poll` on the underlying data,
/// e.g., the `Pin<Box<F>>` type we created this
/// from.
///
/// Unsafe condition: Expects the output from `IntoRawPointer::into_raw`
/// which must not have already been freed.
poll_fn: unsafe fn(*mut (), cx: &mut Context<'_>) -> Ready<()>,
/// Frees the memory for the pointer to future.
///
/// Unsafe condition: Expects the output from `IntoRawPointer::into_raw`
/// which must not have already been freed.
drop_fn: unsafe fn(*mut ()),
}
```
* `dyn(T)` => "a dyn that is the size of T"
* `dyn(..)` => "a dyn of unknown size" (i.e., today's dyn)
* `dyn(?Sized)` -- if you want to ensure no one ever writes it if they don't have to
* `dyn ?Sized + Iterator`
* `dyn(N)` => "a dyn of N bytes"
* in Rust 2024, `dyn` becomes `dyn(usize)`
* `&mut dyn T` ==> `&mut dyn(..) T` is bad
* `dyn T + '_` as often as we can
* `dyn(Word)` where `Word` is a opaque usize thing in libstd that runs drop somehow
* `dyn(*)` ---
* `dyn(T)` means: "it is something viewable as T"
* `dyn(MyPrefix)`
* `dyn(*)`
```rust
dyn trait Foo {
fn foo(&self) -> impl Debug;
// ^^^^^^^^^^
// will become `dyn(*) Debug` when you have `dyn Foo`
//
//
}
```
### Bounding by Send
## 2022-03-21
* RPITIT RFC
* basic question is
* is it `type next: Future`
* or `type next: Fn`
* what would the latter enable?
* "bound by const"
* `Iterator<next: async const Fn(&mut Self) -> Option<...>)`
* `Iterator<const next: Send>` // abuse notation
* `Iterator<async next: Send>`
* `Iterator<async next>`
* write two explainer / guide section
* maybe detailed
* impl plan?
* https://hackmd.io/styPJiN8Rba8QoxDoX7crA
* dynx Trait creation:
*
## 2022-03-17
Lang-team writeup:
https://hackmd.io/gWX7au8sQpG7i0kAUVDKFw
## 2022-03-14
### constrained impl rfc
pretty nearly ready?
specialization: can we phrase it just as "two impls both apply to some set of inputs => one must take precedence"
### sprint mechanics
30 minutes to make more time?
### traits and next sprint
* can simplify dynx by saying "you need to return `impl Future + IntoRawPointer` or just return `impl Future` and we'll make one for you (box)"
* you need to return a smart pointer
open questions:
* declared or not for `dyn trait`
* exactly how do you declare? opt-in vs opt-out vs edition
* how do we model RPITIT
* `type next: Future` (and with which lifetime/type parameters) vs
* `type next: Fn<Output: Future>` (and with which shorthand)
* reference material
* list of changes ("diff")
* implementation plan
* trait multiplication and whether it extends to things beyond auto traits
* `async Foo` `XY` = algebraic shorthand for `X * Y`
lang team
* what we think we have settled
* goals
* Natural usage
* just write `x.foo()` to call, etc
* Allocation (when using dyn) by default, but not required
* Separation of concerns
* you can write a trait that is used by std/no-std/etc
* you can write a `dyn Foo` function that is used by std/no-std/etc
* you can write an impl that is used by std/no-std/etc
* *but* the no-std has to have control of the coercion site
* Ship this year
* try not to take dependencies on wild-and-crazy things
* basic sketch of how you use it
* write an impl with async fn (or -> impl Trait)Prepare
* adapters that do stack allocation etc
* implicit assumption: fairly niche and localized most of the time
* design at 10000 ft is
* impl must return "some pointer"
* with `#[dyn(box)]`
* defaults to box but user can construct themselves too
* basically a `impl Future + IntoRawPointer`
* dyn safe requires `-> impl Trait` but it must be (a) dyn safe and (b) implemented for `Box<T>`
* directions we ruled out and why
* returning fixed number of bytes
* how do you know what's correct?
* always an option with an annotation on the trait level
* always return box
* no-std, perf, etc
* caller decides how to allocate
* niko's argument against:
* sep of concerns
* also, how do we do it? those are either dynamically scoped or requires new parameters that were not present in the trait before --> violates "natural usage"
* note: almost achieved this
* in today's design, you can model it with wrappers -- but you have to do it *before* the dyn cast occurs
* caller decides how to allocate via some dynamically scoped thing
* forwards compatible: we could potentially change the default to use some threaded allocator-like feature, or add a `#[dyn(with_capability)]` mode, etc
* caller decides how to allocate via some global allocator-like setting
* can't do the stack allocation strategy here
* forwards compatible: we could potentially change the default to use some threaded allocator-like feature, or add a `#[dyn(with_capability)]` mode, etc
* key insight:
* if you want to support stack allocation, you need a bound
* in this design, it comes from the `Self` type itself
* cannot orthogonally mix in stack alloc without callee having to know about it
example around lifetimes
```rust
trait Foo {
async fn check_sum(&self, x: &str, y: &[u8]) -> u32;
// in dyn:
fn check_sum<'a, 'b, 'c, 'bound>(&'a self, x: &'b str, y: &'c [u8]) -> Pin<Box<dyn Future<Output = u32> + 'bound>>
where 'a: 'bound, 'b: 'bound, 'c: 'bound;
// nicer desugaring would be something like this:
fn check_sum<'a, 'b, 'c, 'bound>(&'a self, x: &'b str, y: &'c [u8]) -> impl Future<Output = u32> + 'bound
where 'a: 'bound, 'b: 'bound, 'c: 'bound;
async fn check_sum(&self, x: &str, y: &[u8]) -> u32;
type check_sum<'bound>: Future<Output = u32>; // this would be nicer
type check_sum<'a, 'b, 'c>: Future<Output = u32>; // not this
}
```
what about...
```rust
trait AsyncFoo {
async fn stuff(&self) -> impl Debug;
type stuff<'a>: Future<Output: Debug>;
fn stuff(&self) -> Self::stuff<'_>;
}
```
## 2022-03-10
```rust
trait Iterator { type Item<'a>; ... }
```
* `where T::Item: Debug` <-- if you write this,
* `where for<'a> T::Item<'a>: Debug` you get this
* `fn foo<T: Iterator>() -> T::Item` if you write this
* `fn foo<T: Iterator, U>() -> U where for<'a> T::Item<'u> = U` you get the "rough equivalent" of this
* you don't really have an extra `U` parameter
* imagine there was a type `(U where for<'a> T::Item<'u> = U)` that packaged up a variable and some where clauses that constrain it
* this is basically what `T::Item` *is*
* `where F::foo::Output`
* `where for<A> <F::foo as Fn(A)>::Output`
* `where T::Output` today...
* `T: Fn(u32)` you get `<T as Fn(u32)>::Output`
```rust=
fn something<T>
where for<'a> T: Something<'a>
{
T::Bar ==> for<'a> <T as Something<'a>>::Bar ==> <T as Something<'*>>::Bar
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
}
```
## 2022-02-28
What should we do with stakeholders?
* Big picture-- they need it
* Async fundamentals
* RPIT + async fn in traits
* Static semantics
* Desugar to an associated type
* Q: How do you name it?
* Dynamic dispatch
* blah blah blah
* closures
* async drop
* dyn usability
* self
* Portability
* initial async read-write designs
* ... executor ...
* Polish
* Overloading
* ??
* Generators -- basically stalled out
## Next (2022-02-17)
* Send and Sync with dynx adapters
* We only care whether the *pointer* is `Send`(*)
* But we still need to express this about `next` on a `dyn AsyncIterator` type
`(*)` n.b. for async what we *really* care about is that the pointer is either `Send` or points to the embedded async object
* `in('gen) MyPtr: Send` where `'gen` is lifetime of current generator... or `MyPtr: SendIn<'gen>`
## 2022-02-14
dyn explainer
https://hackmd.io/FWQybttJRQ2gH2h0-xwFHA
## 2022-02-10
### Constrained impl RFC
https://hackmd.io/CT6tK-KeR0G3rGsqgOKxlg
### Dyn
```rust
trait AsyncIterator {
type Item;
async fn next(&mut self) -> Self::Item;
fn next(&mut self) -> impl Future<Output = Self::Item>;
}
```
* Range of things we want to support
* inline future (for embedded) -- "wrap some `AsyncIterator` in a wrapper type that pre-allocates (likely on the stack) space for the future"
* "I don't have an allocator or don't want to assume I do"
* cached futures -- keep a `Box` and re-use the storage
* "I am perf sensitive so I don't want to allocate on every call"
* "easy peezy" -- return a `Box` and "make it work god damn it"
* "I just want to call something dang it"
* How to do inline future
* an inline wrapper type `Wrapper<T>` that implements `AsyncTrait` trait for all `T: AsyncTrait`
* its future for a given asnyc method is `InlineFuture<'bound, Output = X>`
* this type wraps a `*mut ()` with a vtable for future
* when dropped it recursively drops the contents but not the `*mut` itself (via a vtable call)
* this can be coerced to a dynx (<-- more on this in a second)
Actual proposal, let's just talk about Future for a second:
```rust
struct DynxFuture<'bound, Output> {
data: *mut dyn DynxFutureTrait<Output> + 'bound';
}
trait DynxFutureTrait {
type Output;
fn poll(&mut self) -> Self::Output;
fn drop_contents(&mut self);
}
```
* Why does dynx have to be built-in?
* It needs to be per-trait
```rust
trait DynxFutureBuilder<O> {
unsafe fn from_raw<T>(x: *mut T) -> Self
where
T: Future<Output = O>
}
trait Dynx<trait Trait>: Sized + Trait {
unsafe fn from_raw<T>(x: T) -> Self
where
T: IntoDynxRaw<Trait>;
}
trait IntoDynxRaw<trait Trait> {
fn into_raw(self) -> *mut dyn Trait;
unsafe fn drop_raw(p: *mut dyn Trait);
}
```
```rust
trait Dynx: Sized {
unsafe fn from_raw<T>(x: *mut T::Target) -> Self
where T: RawDrop;
}
unsafe trait RawDrop {
type Target;
const fn dtor() -> unsafe fn(*mut Self::Target);
}
unsafe impl<T> RawDroppable for Box<T> {
type Target = T;
const fn dtor() -> unsafe fn(*mut Self::Target) {
|p| unsafe { drop(Box::from_raw(p)) }
}
}
```
```rust
impl Foo for Bar {
// returns a fully statically known type
async fn something(&mut self) { }
}
impl dyn Foo for Bar {
const fn something() -> fn(&mut Self) -> impl ThinPtr<Target: Future<Output = ()>> {
|self| Box::new(self.something())
}
}
impl Foo for Wrapper<Bar> {
// returns a fully statically known type
#[dynx]
async fn something(&mut self) {
}
}
```
## 2022-02-07
### Async overloading
Idea, work through example in more detail.
nrc + yosh worked through it and concluded that it can't be as nice as swift, at least not today.
concrete next steps: blog post explaining the background. Yosh had one but there's lots of space uncovered.
niko: aware of representation of library types as an isuse, not sure what else exists.
yosh: futures don't have to be awaited at the point where they are constructed, have to apply an uncomfortable amount of inference, or annotate it at the import site. So when you say `use std::io::Read` would have to indicate that it's "async", so that the import becomes unambiguous and calls become clear.
niko: What are we trying to achieve? I think enumerating the goals is important.
tmandry: many different versions, like the "shallower" version (name resolution overloading) that Swift does, and a deeper version where you are threading context down for each function. In the deep version, if A calls B calls C, then A can influence which mode C should take. Not clear whether that can work, each has pros/cons, but we should be very explicit about the difference.
niko: obviously you want deep!
yosh: obviously you want shallow!
yosh: main problem this tries to work on is having two versions of each type exposed in the public interface. Talked to dot-net team, and their #1 concern was having 2 of each type. Really dislike the split. Similar experience from node.js. It's a big thing.
niko: I care about the sandwich problem (async -> sync -> async). example of this is iterators (combinators), anything that has a callback.
yosh: async-std was pushing on how hard you can go -- what are the missing language features -- async closures, async drop, cancellation. Main place where things are different are for concurrency.
niko: kotlin calls async "suspended functions". generalization of sync. offer ability to build and compose more interesting control flow in a fine-grained way. not necessarily tied to I/O, just happens to be a common need.
yosh: usually bottoms out in I/O
niko: examples are... race between two algorithms. some things you might otherwise use threads for.
the issues niko is talking about
status quo story:
```rust
async fn do_something() -> eyre::Result<()> {
let result: Vec<_> = widgets
.iter()
.map(|x| x.await?) // what Niko wants: await? here
.collect()
// what Niko wants is that the above is equivalent to
let mut output = vec![];
for widget in widgets {
let result = widget.await?;
output.push(result);
}
}
async fn do_something() -> eyre::Result<()> {
let result: Vec<_> = widgets
.iter()
.map(|x| x.await?) // what Niko wants: await? here
.collect()
// what Niko wants is that the above is equivalent to
let mut output = vec![];
for widget in widgets {
let result = widget.await?;
output.push(result);
}
}
async fn something_or_other() {
item.verbose(|| something.await);
}
fn verbose(&mut self, x: impl FnOnce()) {
self.verbose = true;
x();
self.verbose = false;
}
```
- [cpp specialization dyn dispatch example thingy](https://godbolt.org/z/zKcss4sd6)
One problem with "deep" overloading: you have to consider every/many function calls as potential await points, and your behavior with local variables is exposed to async callers
```rust
fn verbose(&mut self, x: impl FnOnce()) {
self.verbose = true;
let non_send_thing = ...;
x(); // <-- could await?
write(...); // <-- could await?
drop(non_send_thing);
self.verbose = false;
}
```
another concern: `?` makes control flow visible, want TCP to prob do the same
```rust
fn verbose(&mut self, x: impl FnOnce(&mut Self)) {
self.verbose = true;
x(self); // <-- if this "breaks" with `?` out of the enclosing function...
self.verbose = false; // <-- ...still want this to run
}
fn verbose(&mut self, x: impl FnOnce(&mut Self)) {
let guard = VerboseGuard(self);
x(guard.this);
}
```
relevant [status quo story](https://rust-lang.github.io/wg-async/vision/submitted_stories/status_quo/barbara_bridges_sync_and_async.html)
[Yosh on halt-safety](https://blog.yoshuawuyts.com/async-cancellation-1/#halt-safety)
### Where to go next on contexts
## 2022-01-27
* impl-trait correspondence RFC
* https://hackmd.io/CT6tK-KeR0G3rGsqgOKxlg
* dyn details
### apit in dyn
* specifically `arg: impl Trait`
* could later add `arg: &impl Trait`
* but prob not `arg: Vec<impl Trait>`??
* vtable encodes "dynx value"
* a pair of a pointer + a vtable where the vtable is for the "dyn version" of the trait
* and includes how to drop the pointer itself
* at the call site, you need to adapt from the `impl Trait` to the dynx
* why dynx? why not e.g.
* `&dyn Trait`
* `&mut dyn Trait`
* some possible reasons:
* `&dyn Trait` maybe doesn't implement `Trait`
* `Trait` may have methods that are not `&self`
* `where Self: Sized` on the trait??
* have to think about that one
* maybe it would be better to say
* use `&dyn Trait` if it implements `Trait`
* `<dyn OtherTrait as OtherTrait>::method(...)`
* that's where the adaptation happens
* return side:
* dynx serves clearer purpose
```rust
trait MyTrait {
async fn foo(&self);
}
struct InlineMyTrait<'me, T: MyTrait>
where
T: 'me,
{
underlying_impl: T,
foo_storage: MaybeUninit<T::foo<'me>>,
}
impl MyTrait for dyn MyTrait {
type foo<'me> = dynx MyTrait + 'me;
}
impl<'me, T: MyTrait> MyTrait for InlineMyTrait<'me, T>
where T: 'me
{
type foo<'me> = &'me move T::foo<'me>;
// this is not what goes in the vtable
// table has a light shim that takes your `&move` and 'packages it'
// as a dynX
fn foo(&mut self) -> &move T::foo<'_> {
let x: T::foo<'_> = self.underlying_impl.foo();
self.foo_storage = unsafe { std::mem::transmute(x) };
&move self.foo_storage.assume_init()
}
}
fn main() {
let v: impl MyTrait = InlineMyTrait::new(something());
}
```
Visibility check:
* At the impl, we check that:
* If this trait is dyn safe, we must be able to construct a vtable
* So:
* The trait must be nameable from the point of the impl
* So that we can make the dynx newtype
* But maybe you will never use the impl as a dyn
* in which case it would have been ok to have a non-nameable trait
```rust
// crate A
pub trait Foo {
fn foo() -> impl sealed::Bar;
}
mod sealed {
trait Bar { }
impl Bar for u32 { }
}
// crate B:
// compilation error:
// because `a::sealed::Bar` is not nameable
impl A::Foo for MyType {
fn foo() -> u32 {
}
}
```
## 2022-01-24
* Structured *concurrency*
* https://crates.io/crates/async_nursery
* Cancellation + aturon
* Niko to write a blog post
* Dyn and digging into APIT
* RPITIT
* Santiago is working on an impl strategy
* RFC:
* Questions:
* Right set of generic parameters to have on the GATs we introduce
* Especially
* Async overloading
```rust
trait Foo {
fn bar(&mut self) -> impl Debug;
type bar<'x>: Debug;
fn bar(&mut self) -> Self::bar<'_>;
// do not want async fn to desugar as follows:
async fn baz(&mut self, db: &Database, data: &String);
type baz<'a, 'b, 'c>: Future<Output = ()>;
// want it to do something more like this:
async fn baz(&mut self, db: &Database, data: &String)
fn baz<'a, 'b, 'c, 'z>(&'a mut self, db: &'b Database, data: &'c String) -> Self::baz<'z>
where
'a: 'z,
'b: 'z,
'c: 'z;
type baz<'bound>: Future<Output = ()>;
}
fn foo<'d>(tcx: TyCtxt<'tcx>, data: &'d Vec<...>) -> impl Iterator<Item = &'d Somethings>
where 'tcx: 'd
{
MyIterator { tcx, data }
}
// explicit capture was intended to work like this, but that's
// not ideal because it still has to talk about tcx:
type Foo<'d, 'tcx> = impl Iterator<Item = &'d>;
fn foo<'d>(tcx: TyCtxt<'tcx>, data: &'d Vec<...>) -> Self::Foo<'d, 'tcx> {
MyIterator { tcx, data }
}
// but dyn is nicer:
fn foo<'d>(tcx: TyCtxt<'tcx>, data: &'d Vec<...>) -> Box<dyn Iterator<Item = &'d> + 'd>
where 'tcx: 'd
{
Box::new(MyIterator { tcx, data })
}
type Foo<'d> = impl Iterator<Item = &'d>;
fn foo<'d>(tcx: TyCtxt<'tcx>, data: &'d Vec<...>) -> Foo<'d>
where 'tcx: 'd
{
MyIterator { tcx, data }
// hidden type is MyIterator<'d, 'tcx> which doesn't fit
// what if the hidden type could be
// exists<'tcx: 'd> MyIterator<'d, 'tcx>
}
// in async fn this can crop up too:
// https://github.com/rust-lang/rust/pull/89056
```
* lifetimes....
* the bound
`Foo + 'x`
```rust=
fn foo(x: Foo) -> Bar { }
fn foo(x: Foo<'_>) -> Bar<'_> { }
fn foo(x: Foo) -> Bar from x { }
fn some_place(x: Foo) {
let p = foo(x);
...
drop(p);
}
struct Foo<'a> { }
struct Bar<'a> { }
```
* RPITIT RFC:
* withdrew because of general concerns
* extensions (uncontroversial):
* we want to support `fn foo() -> impl Bar` in trait
* but permit `fn foo() -> u32` in impl
* and allow users of impl to rely upon
* but:
* do we expose the return type or the fn type
*
* existentials are not blocking for RPITIT, only for accessing the names of futures returend by async fn
* and not in the `Output` version
* this is a downside
* alternative:
* associated type represents the function
* `fn foo() -> impl Trait`
* `type foo: Fn<(A0...An), Output: Trait>`
* plan:
* General impl "refinement" RFC?
* tmandry to do initial draft
* Reintroduce RPITIT RFC (without naming return type)
* Later RFC to cover naming return type
* specialization revamped
## 2022-01-22
* tmandry to do the rename, then open PR to post blog next week
* tmandry to do initial member screening
* https://hackmd.io/VQ00Ry-PRmiTf1za3O7OkA
* Dyn
* [Sealed traits](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d6e0ea738aea7a808aff7f5e6c1c0d41)
* Can't safely expand the set of things that implement this trait to `dynx`
* Ok if we respect the rule that anyone needing this must be able to name the trait?
## 2022-01-13
### blog post something
Pattern we observed in the blog post was
```rust
trait Foo {
type Bar: Bar;
fn foo(&self) -> Self::Bar;
}
trait Bar {
}
```
where `foo()`, in dyn case, should return `Box<dyn Self::Bar>`
But be careful about input types.
### stakeholders meeting
Ideas
* async drop
* narratives: what about your experience is not included?
### async overloading
* sort of an implicit monomorphization mode
Sketch 1:
```rust
fn foo(f: impl FnOnce()) {
// "either mode"
//
// can only call other `fns`
f()
}
async fn foo() {
// specifically async mode, yields a future
}
async fn foo() {
foo(|| {
foo.await
})
// specifically async mode, yields a future
}
```
Is the right way to think about this a "capability"
```rust
fn foo<'a, T>() -> Box<dyn BlahBlahBlah + 'a>
where
T: in('a) { BlahBlahBlah }
{
}
fn bar() {
let f = foo();
f();
}
```
Problem:
* when I call a `dyn` method, I don't know what capabilities it required, I only know they exist
* challenge is that to compile something so that it might await
Another version:
```rust
async? fn foo(f: impl FnOnce()) {
}
```
Something
```rust
fn foo<T>() -> Box<dyn with(Async) FnOnce>
```
```rust
fn foo<T>() -> Box<dyn with(ControlFlow) FnOnce()>
```
```rust
fn foo<effect 'a, T>(x: with('a) impl FnOnce()) -> Box<dyn with('a) FnOnce>
```
```rust
impl Iterator {
async? fn map<F, U>(x: F) -> async? Map<F>
where
F: async? FnMut(Self::Item) -> U,
{
}
}
struct Map<F: FnMut(Self::Item) -> U, U> {
}
```
```rust
with(Async) Map
```
#### Tyler's notes
Touched on:
* Async as an effect
* Effects as capabilities/contexts
* With clauses on types
* Or does it all just boil down to with clauses on their generic trait bounds?
* "Everything is async"
* Or everything is a generator, or some generalization of such
* Optimizing to regular function call ABI when awaiting isn't used
* Also finding a way to inline calls in a way that allows the state machine to be optimized away (using knowledge of the calling code this time)
## 2022-01-10
### membership needs cleanup
* nrc: working group membership needs cleanup
* active participation in last 6 months, where participation is one of the following
* attended triage/sprint meetings
* opened PRs
* mentored people to fix polish issues
* reviewing polish issues
* led an initiative
* rename working group to
### Niko complaints about Dada
OMG look at these types
```
pub(crate) type ThunkClosure = Box<dyn for<'i> FnOnce(&'i Interpreter<'_>) -> DadaFuture<'i>>;
pub(crate) type DadaFuture<'i> = Pin<Box<dyn Future<Output = eyre::Result<Value>> + 'i>>;
```
related to:
https://rust-lang.github.io/wg-async-foundations/vision/submitted_stories/status_quo/barbara_polls_a_mutex.html
https://dada-lang.org/
### 2021 year-end blog post
#### What is goal of this blog post?
* To raise excitement and awareness about what we're doing
* To communicate our vision for an easier async Rust for the on-going efforts
* Async fn that is everywhere
* Generators
* Portability -- just write `async fn main`, maybe `std::io::write` or something
* Polish
* Tooling
#### Short shiny future story
* Issue aggregator
* iterates issues, prints out something
* Now want it to work over github + gitlab
* introduce a trait
* use dyn dispatch to be cool
#### Ongoing efforts
* Async fn in traits
* point...somewhere? at the updates?
* Dyn trait
* point at Niko's blog post
* Portability
* point at nrc's blog post
* Generators
* point at esteban's blog post
* Polish: capture
* point at eholk's PR
* Polish: lints
* point at someplace or another
https://github.com/rust-lang/async-crashdump-debugging-initiative/blob/master/CHARTER.md
### Organization
https://github.com/rust-lang/team/blob/master/teams/wg-async-foundations.toml
* AI(tmandry): Write up request for Github support for pages redirects
* AI(tmandry): Clean up wg membership
#### Debugging and profiling
Awesome efforts...
* Crashdump debugging
* Tokio: console
####
### dyn + follow-up