Async fundamentals ere long ago (2021)
2021-12-20
Problem with with
clauses
Not good enough.
* Send
?
set interpretation
with(cap1, cap2)
vs with(cap1) with(cap2)
with(Bound)
means "all capabilities meet Bound
"
- default for an impl is empty set
with(_: Send)
with(Send)
with(cap1: Type)
or with(cap1: impl Trait)
:alternative-universe.gif:
- What if we ONLY HAD
Send * Bar
and no +
…
- then you could do
where T: Send * Debug
// in either order
where T: Send, T: Debug
// distinct thing
- Meaning:
T: Foo * Send
means "all RPITIT in Foo are Send"
Some form of opt-in for non-RPITIT?
2021-12-16
year end blog post
What are the pieces we need to make this beautiful code work
- Async fundamentals
- Portability
- async fn main
- tcp stream or something
- Generator working group
- Async iterator trait
- Generator syntax
- Await patterns
- Polish / tooling
- Beautiful diagram
where we were
tale of two dyns
goals
- supporting
async fn
calls;
impl Trait
in argument and return position, so long as Trait
is dyn safe;
- supporting "by value"
self
methods;
- supporting cloning of trait objects[^caveat];
- supporting trait objects that are "partially dyn safe".
(what other things do we want for dyn that we are not trying to get now)
use cases
- embedded 1
- library uses async fn in a trait and dyn trait
- embedded user of library is able to invoke it without calling box to box futures
- by e.g. inlining OR the enum solution?
- tight loop with reuse
- reuse: invoke
foo.bar()
a lot for the same foo
- something something uses
&mut
and avoids alloc
- regular code
- uses box, it's easy, it can clone things
- async drop
- embedded async drop
- tight loop up to N bytes
- can handle this by making your own trait and doing the erased serde
- question mark:
- measure the size of futures?
dyn Trait
it has a vtable that includes
- all the regular fns like today
- for these cases…
- argument position…
- what we need
- define how the caller will "package up" the argument in the way the vtable expects
- how the vtable will "unpackage it" for the underlying fn
- cases
- Impl trait in argument position
- vtable expects a dynx
- dynx where the pointer type is
- &
- &mut
- or Box
- depending on our analysis of the trait
- By value self in argument position
- vtable takes a
*mut T
, effectively &move
- iow, callee owns the
T
but not the memory the T
resides in
- vtable shim does
let x = *x
and then calls underlying fn with x
- perhaps some more efficient version
- return position…
- what we need
- define how the callee will "package up" the argument in the way the vtable expects
- how will the caller "unpackage it"
- By value self in return position
- vtable: always takes an out pointer
- compiles
*outptr = underlying_fn()
- no normal caller :)
- because
foo()
fails to type check if return type is ?Sized
- but there is an intrinsic that we add
std::ptr::write
basically
- takes a
F: FnOnce() -> T
and a *mut T
(or whatever) and calls the function, writing its result into the raw pointer
- now we can write
Box<T: Clone>
impl to use this intrinsic
- Eh! Problem
trait Clone: Sized
means we can't have dyn Clone: Clone
, wtf do we do
- Impl trait in return position
- vtable returns a dynx
- callee can use box
- for the case of "inline wrappers" etc
- we could customize the mapping
- for each of the above:
Impl trait in arg
you wrote
2021-12-13
RPITIT
Bounding function types: What about const
bounds?
things to think about
- in favor of the associated type being return
- but what doesn't work
- can't impl traits or add assoc types for fn types
- can't do const as a trait as above (Seems nice)
- if you have
-> Vec<impl Trait>
or -> (impl Trait, impl Trait)
, feels weird
- whereas
::Output
is very clear about what it is
dyn
Parts of the plan, in rough order (can stop at any point)
- Allow
&dyn Len: Len
and so on (see below)
- ABI hackery to make by value self work:
-> Self
via out pointer
- Allow
self
via "in pointer" and some stuff
impl Trait
in argument and return position: subject to vtable strategy (can be "always dynX
") and caller strategy
- caller strategy could be something like
dynX<Box>
- TODO: outline ways we could do this now, maybe with newtypes
- Allow strategies to choose arbitrary pointer types by abstracting with
dynX
- Allow "slicing" traits with
dynX &Trait
- Make
dynX
the newer, easier dyn
&dyn Len: Len
We can get pretty far with these, I think
(may require opt-in)
playground
DerefOwn
- Allows moving out of a smart pointer
- But requires more steps than Deref/DerefMut
playground
2021-12-09
- RPITIT implementation
'static
hack for lifetimes inherited from parent
- We need to move these desugarings to something you would actually write
- In particular, I'm concerned about
where T: Foo<'a>
when 'a
does not appear in the bounds – when we use 'static
, that becomes T: Foo<'static>'
, but that is not true
- Desugared type alias has a conceptually different set of generics from the function; we should just copy them
New RFC
- You can write
fn foo() -> impl Trait
in traits and in impls
- it desugars to
* type foo<...>: Trait
in a trait
type foo<...> = impl Trait
in an impl
- You can write
fn foo() -> type
in an impl
- but do you have to write
type foo = type
too?
- if defined using impl trait in the trait, add it?
- what happens if you put
type foo = impl Trait
and fn foo() -> type
?
- impl has to match signature from outside the defining scope
- how many lifetime parameters does the associated type get?
- specifically for async fn, change the desugaring to add an extra fn parameter
'x
which is
- introduce shorthand for
T::foo
where foo
is a GAT
- expands to
for<'a> T::foo<'a>
Dyn
- dyn trait design outline
- supporting clone for
Box<dyn Clone>
- extend
Clone
with an unsafe method
unsafe fn clone_to_raw(&self, *mut ())
- extend
impl<T: Clone> Clone for Box<T>
to be
impl<T: Clone + ?Sized> Clone for Box<T>
- you call
clone_to_raw
- another way to think about this
- in the vtable for a
-> Self
fn, we put a fn that expects a *mut Self
and "does the right thing"
- need some intrinsic to call it
- Rc's copy-on-write methods e.g.
Rc::make_mut
- could make that work for
T: ?Sized
- if you want to support
-> impl Trait
with dynamic dispatch
- you want to return a
DynX<Trait>
- in other words:
- "some pointer to something that implements Trait"
- "and has a vtable slot for how to drop that pointer"
- vtable interface:
- in procedural macro, there wasn't one vtable interface
- it was defined by this erased trait that carried the methods we wanted
The dyner design
Introduce a DynX<Trait>
type into the standard library:
- by some magic it holds a
*mut Trait
and knows how to drop itself
The vtable for an impl of a trait:
- If method has
-> impl Trait
, vtable expects to return a DynX<Trait>
- each impl will require some "glue" to take the actual return value and make it a
DynX<Trait>
- for example, if it returns some future type
F
- that may need to be
Box::pin
'd to get a pointer Pin<Box<F>>
- and then we attach the vtable to get a
DynX<Future>
- at the point where we generate the vtable for a given type:
- there is a point in compilation where we are coercing from a known type to a dyn type
- and that is where we do whatever adaptation we have to do
- how do we determine that adaptation?
- for now just assume there is an oracle
Oracle(Trait) = adaptation
- somehow can be overriden on a per-impl basis
- some fixed set of strategies you can pick from
- If method has a
impl Trait
argument, table expects a DynX<Trait>
as well
- as above, there is some glue, but this glue has to be the same for all instances of the trait
- has to be customized per trait or perhaps some "wrapper" type
- we could probably do some kind of
&move
thing
- so long as trait doesn't have a
'static
bound
- If method returns
-> Self
, we .. do something like we descirbed earlier so that you can invoke this with a custom return pointer
- the vtable has a method takes an explicit
*mut Self
- the vtable can do whatever ABI needs to make this work
- need an intrinsic to call a fn whose return type is
?Sized
and you supply the pointer where the result goes
- at monomorphization time, if return type is sized, this is just a regular call (
std::ptr::write(...)
)
- if it is unsized (e.g., dyn) we invoke the vtable method
- we do the right for
[T]
whtaever that is (memcpy?)
Are the idea of &Trait
bounds important?
You write dyn trait
on your trait and we enforce:
- it will add
impl<T> Trait for &T
and friends (not really needed, because we are passing a dynx now)
- you only use
impl Trait
with other dyn trait traits
- niko wants this for soundness
- individual impls can't "fail" to produce a vtable
- would be a post-monomorphization failure
- but they could "panic" in
2021-12-06
tmandry with
blog post draft: https://hackmd.io/LkzoeLaaQV2MMh512WSG7A?view
https://github.com/nikomatsakis/dyner/blob/main/src/dynerx.rs
what is the "built in" story
- you will create a
dyner Trait
from some "pointer to T where T: Trait"
- "pointer" is anything that supports
into_raw
and from_raw
- e.g. could be
Box<T>
or Rc<T>
or &T
or &mut T
dyner Trait: Sized
dyner Trait: Trait
- but wait: for
Rc<T>
or T
, you can only get &self
- for
&mut T
, can only get &self
or &mut self
- for some traits, that's okay
- so
&'a T
can become a dyner Debug + 'a
- but for other traits, not so ok
- but a
&'a T
cannot be made into a dyner Iterator
- answer: introduce
&Trait
and &mut Trait
bounds
- intuitively these are either "views" on to Trait that select a subset of methods
- or perhaps they are equivalent to
Deref<Target: Trait>
and DerefMut<Target: Trait>
- but in any case you can make a
dyner &Iterator
from &T
- whichever way:
dyner &Iterator: Deref<Target = dyner Iterator>
- observation:
&dynx Debug
has "double indirection"
- if you have an
&impl Debug
, and you want to use it in dyn form without double indirection
- you could do
dyner Debug + Copy
(for this case)
impl/dynx symmetry:
observation:
each trait has a "natural pointer type"
- if only
&
or *const
, then &
- if only
&mut
or *mut
, then &mut
- else
Box
Questions:
- How do you create a
dyner
? Coercion? Is there an explicit form?
- If the answer is coercion, what can you create a
dyner
from:
- from the pointer? (most explicit, need to have this option)
- from the pointee? (requires auto-ref of some kind)
- maybe sometimes one, sometimes the other?
If we limit ourselves to pointer, then, if you start with this code:
and you change foo
to use dyner
, you start to get errors:
arguably that is the wrong code, and what you wanted is
maybe we just want to make that more syntactically pleasant.
Niko's conclusions:
- Trying to make a super smart coercion will be a leaky abstraction and wind up both unsatisfying and confusing
- We need another mechanism to avoid monomorphization costs (e.g., dyn type parameters)
- We should have coercions from a pointer
- we might want an explicit form like
expr.dyner
, plausibly that could do auto-ref though
Challenge is that we sort of want a few constructors. Function of both the trait and the pointer.
(pointer) |
Trait has &self |
Trait has &mut self |
Other |
Box |
Trait |
Trait |
Trait |
&T |
Trait |
&Trait |
&Trait |
Rc<T> |
Trait |
&Trait |
&Trait |
&mut T |
Trait |
Trait |
&mut Trait |
The set of traits that the pointer must implement depends on the Bounds
in dyner Bounds
:
- Must always implement
IntoRaw
(a new trait afawk)
- If
Bounds
includes has an element with a self: &Self
method:
- If
Bounds
includes has an element with a self: &mut Self
method:
- If
Bounds
includes has an element with a self
method:
- must implement
IntoInner
(a new trait afawk)
- If
Bounds
includes has an element with a Box<Self>
method:
- If
Bounds
includes has an element with Pin<&Self>
method:
- must implement
Pinned
(where Pinned
is an unsafe trait implemented by Pin<P>
)
- must implement
Deref
- If
Bounds
includes has an element with Pin<&mut Self>
method:
- must implement
Pinned
(where Pinned
is an unsafe trait implemented by Pin<P>
)
- must implement
DerefMut
- If
Bounds
includes has an element with Pin<Box<Self>>
method:
- must implement
Pinned
(where Pinned
is an unsafe trait implemented by Pin<P>
)
- must implement
DerefMut
- must be
Pin<Box<?>>
less is more design:
dyn trait Trait
- automatically provides the
impl Trait for &dyn Trait
impls
- automatically provides the
impl Trait for &mut dyn Trait
impls
- automatically provides the
impl Trait for Box<Trait>
impls
- errors if Trait is not dyn safe
- Question:
- how does it handle
-> impl Trait
?
- Answer: Trait must also be
dyn
- the associated type for
dyn Trait
is hardcoded to Box<Trait>
- generates the vtable shim; we know that
Box<Trait>: Trait
because dyn trait
guaranteed that
- how does it handle
impl Trait
in argument position
- Answer: Trait must also be dyn
- can invoke with
&x
/&mut
/Box::new
of the parameter depending:
- if the trait has only borrowing methods and does not require
'static
, can use &
or &mut
- else must use
Box
- what about
-> Self
? (notably Clone
)
- final methods can be handled and have no restrictions
- or even defaulted – we just say that the default is what gets invoked
- probably with some opt-in
- replaces
where Self: Sized
niko noodles post meeting
- challenging cases
- impl trait in APIT:
- in general case need something like
Box
- how to make this "no-std compatible"
- what type does the thing in the vtable expect?
- something like the dyner version would be good
- it has a
*mut dyn Trait
and a vtable extension that lets it be dropped
- what about
-> Self
?
- we want to return the same type we got in – well, probably?
- so if you have
Box<dyn Trait>
you want Box<dyn Trait>
afterwards?
- is that true? I often have
&T
and I invoke clone to get T
- maybe with
&dyn Trait
I want Box<dyn Trait>
- then conceivably implement Clone for
&dyn Trait
where Trait: Clone
that returns Box<dyn Trait>
- vtable would include a wrapper that
2021-12-02
https://dyner.netlify.app/faq#does-dyner-have-a-theme-song
with clause post
Built-in dyner
What is dyner…
- you make a type
DynFoo
that represents a particular "configuration" of dynamic dispatch
- how to "box"
-> impl Trait
- how to "box" the dyn value itself (if you do box it)
- this type encapsulates the pointer + the pointee (so it is sized)
- i,e., you don't do
Box<DynFoo>
, you just do DynFoo
(or &DynFoo
etc)
- if you make a
DynFoo
from an &impl Foo
you get a Ref<DynFoo>
, which only supports Deref
trait
- side note: can prob get rid of that "bit twiddling" to track whether to free
- maybe in the future
- which functions to "devirtualize"
- instead of
where Self: Sized
, making a 'default' that runs
- ergonomic easy path
let's use virtual
today I write:
to make it dynamic today I have to add &mut
both places
this would be nicer..
https://pol.is/report/r2p6mj8dka3kpsnymv6aj
https://rust-lang.github.io/async-fundamentals-initiative/evaluation/challenges/dyn_traits.html
https://rust-lang.github.io/async-fundamentals-initiative/design-discussions/dyn_trait.html
https://rust-lang.github.io/async-fundamentals-initiative/design-discussions/dyn_async_trait.html
dyn &Future
dyn &mut Future
&dyn &Future
=> &dyn Future
&mut dyn &mut Future
=> &mut dyn Future
dyn &Future: Deref<Target = dyn Future>
2021-11-29
stakeholder meeting retrospective?
- publish minutes "as is" / or try to write a summary
- email people that we plan to, give them a chance to raise any concerns
- defer: write a blog post that tells about the lessons and experience and summarizes
- https://hackmd.io/SwLsaDDmTSCLKQfE2DYk1Q
RPITIT / async fn
How Niko is thinking about things
Step -1
What is missing from the impl trait in traits RFC?
Step 0
Step 1
Status for GATs as of a few weeks ago:
- Algorithm for finding which where clauses ought to be included by default on GATs
- Current compromise:
- force user to write where clauses explicitly
- thus backwards compatible to make them appear by default OR make them not required
Step 2
Step 3
But what if you want to know "what kind of item is going to be producted by the iterable that iter returns"… IntoIterator
has IntoIter
(type of iterator that is produced) and Item
(type of item yielded by that iterator).
Step 3.5
Step 4
Step 5
What does this need?
- OK trait and our handling of WF would have to be tweaked
- callee gets to assume its where clauses are well formed
- not just its input arguments
- tweak to "implied bounds"
foo
existential lifetimes in impl trait
https://github.com/rust-lang/rust/issues/60670
with types?
2021-11-19
Stakeholders retrospective
- Call on people to give feedback
- Got a little sidetracked
- Need to talk about overall roadmap
2021-11-18
'_
in where clause == forall
- turbofish rules for these GATs:
- if there are APIT, cannot use turbofish
- if you do not supply any lifetimes: use
'_
for all of them
- if you do, there cannot be any late-bound lifetimes (else error) (this rule isn't actually needed for this idea)
- else must supply all
- must supply all type parameters
- for other GATs:
2021-11-15
- Niko wrote some stuff
- Bridging poll traits with async traits
- Hard with multiple poll functions
- Something something resume args ((with?))
- Easier with single poll: inline async fn
2021-11-11
Action items
- (Niko by Monday) Turn outline into something for the repo
- (Spastorino by Monday) "code"
- Future: Writing up issues for things like inline etc.
Notes
- Going to pursue RPITIT to start
- what we will ship
- stage 0
- stable compiler supports impl Trait and GATs
- stage 1
- stable compiler can compile async fn in traits
- crate published by rust-lang available on crates.io for dynamic dispatch
- dyner
#[dyner] trait Foo { }
creates DynFoo::new(...)
that implements Foo
- supports:
- traits with async fn
- traits with
-> impl Trait
if dyner
is used on Trait (maybe?)
- traits with
(impl Trait)
if dyner
is used on Trait (maybe?)
- includes:
- "Dyner"-ified versions of libstd traits like AsyncRead and friends
- stage 2
- libstd will add stable versions of
- AsyncRead, AsyncWrite, AsyncStream
- (domain of the portability initiative)
- stage 3 and beyond
- based on experience with dyner we figure out an official dyn story
- "Someone is coding with dyner…
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
"
- async closures
- other fun stuff
- what can you do with this
- Grace and Alan are hacking on their async rust service at SomeBigCo
- Traits they use internally just use async fn
- If they want dyn dispatch, they use
#[dyner]
- To talk about read/write traits, they use the dyn-ified versions from dyner
- Barbara has a cool idea for an arena-ified alternative (or one that caches .. or something .. )
- she forks dyner and writes her own punny named crate that embodies this pattern
- she submits the link to the repo
- it's collected and in the doc and when the async fundamentals team gets to stage 3 they use a variation of this brilliant idea in their final design
- Grace is working on her embedded project, she can use the stable async fn support to get static dispatch
- She has a plan for dyn dispatch and experiments and publishes her own procedural macro crate which the async fundamentals group adds to their repo
- Opens a PR with an experimental of version of embedded-dyner
- Hyper publishes a "hyper-dyner" crate that includes dyn-ified versions of its traits
- or maybe Sean Monstar opts for a feature flag. Whatever dude. You do you.
- Niko's wild and crazy Rust 2024 fantasies
- "feature previously known as dyn" => low-level primitive, maybe it is even replaced with existential types or something
- observation: if dyn is a low-level primitive, "dyn-safe" is silly, you want to expose as many capabilities as you can
- "low-level"? not the thing most code reaches for first
- sort of like
for<'a>
- you have
dyn trait Foo
that exposes some "pretty useful" version (probably with Box) that is ergonomic and good enough most of the time
- ability to declare your own variations in other crates (with traits you didn't define and don't have to copy and paste) and use those
- you have "existential types" (which replace today's
dyn
) as the "fancy version" to build those nicer, ergonomic versions with
- "inline async at impl site" ??
- don't recurse!!!
- add in a panic at runtime
2021-11-07
WIP updates
RFC
- Should we talk to cramertj?
Stakeholders
Looking at https://alternativeto.net/software/doodle/
Implementation
Can use type $ = impl Foo;
to implement RPITIT, and then implement async fn
desugaring in terms of that.
Syntax for named fn types
- It would be nice to have explicit and anonymous generics be equivalent
- but it doesn't seem to work very nicely
- you need
decltype
and then declval
- We can make anonymous generics late-bound
- Hopefully explicit generics too, eventually
- Still need to support
let f = foo::<i32>;
- Requires making some "wrapper" to hold the specified generics. i.e. type currying
2021-11-03
RFC conversation
cramertj comment
Will we be able to do this in the future?
Can libs assume it will be possible in the future? Backcompat hazard…
Implementation
Maybe faster to implement RPITIT first.
https://github.com/rust-lang/impl-trait-initiative
https://rust-lang.github.io/impl-trait-initiative/updates/2021-oct.html#type-alias-impl-trait-stabilization
2021-11-01
Sprint goals review
- Get MVP RFC to FCP
- Implementation:
- Santiago has done a basic desugaring but tests are failing still
Stakeholder meeting agenda
- 90 minutes
- Who will present? tmandry mostly
- Outline
- Format
- We'll talk
- You ask questions whenever they come up, no need to wait
- Async vision doc overview (keep it short)
- The grand roadmap:
- Scoped APIs for reliable concurrency
- Common interop traits are just "async fn"
- Our part today:
- Other active initiatives:
- Generators async and otherwise
- Tooling (tokio console, crashdumps)
- Polish (the bug! backtraces!)
- What does async fn in traits mean
- Async fn: fn that returns impl Future
- impl Trait in traits desugars to associated type
- Send bounds and named return types
- The syntax we expect
- Where would this be needed
- Feedback point:
- What have your experiences been with Send bounds in generic code so far?
- Do your codebases frequently use
spawn
or other parallel operations?
- This is the MVP:
- You can write async fn in traits
- It works in static cases
- Writing the Send bounds for futures is kinda painful but possible
- Major capability missing: dynamic dispatch
- Associated type prevents dynamic dispatch
- Also, boxing
- Not sure the most general fix here
- Current approach:
- Feedback point:
- Where do you use dyn dispatch vs static in your codebases?
- Shortcomings of MVP and future work
- Dynamic dispatch in the lang
- Shorthands or better ways to put send bounds
- Multiplication bounds (!)
- Globs
- Implementation status
- Core features are on path to stabilization
- Async sugar is on its way to nightly
- We'll let you know when it's time to port your code to try this out and give instructions for how to do it
Niko's crazy dyn idea
dynx Trait
- "A pointer to
Trait
that implements Trait
"
- whereas:
dyn Trait
= exists T. Implemented(T: Trait)
- whereas:
dynX Trait
= exists T. sizeof(T) == sizeof(usize) && Implemented(T: Trait)
- probably want to combine this with:
impl<T, U> Trait for T where T: Deref<Target = U>, U: Trait
– automatic for traits with only &self
methods
impl<T, U> Trait for T where T: DerefMut<Target = U>, U: Trait
– automatic for traits with only &mut self
and &self
methods
impl<T: Trait> Trait for Box<T>
– always true
- just imagine this is true for now
- what does this mean?
Box<dyn Iterator>
== dynx Iterator + 'static
Rc<dyn Debug>
== dynx Debug + Clone + 'static
&'a dyn Debug
== dynx Debug + Copy + 'a
- for any dyn safe trait
Trait
we want MyTrait
to be dyn safe, but we can't put foo
into the vtable because it requires monomorphization
because we know that &T: OtherTrait
where T: OtherTrait
, however, we could put this in the vtable:
and then in the impl MyTrait for dynx MyTrait
impl use this function:
Of course, this doesn't really require dynx
. You could do same transformation with dyn
, if you know that &T: Trait
where T: Trait
. You put this in the vtable:
and this in the impl MyTrait for dyn MyTrait
:
Step back and just talk about dyn and erased serde
What could we do?
We could write:
The final "knot" is that one can implement
Using all these pieces, if you have a &T: MyTrait
, you can …
- convert the
&T
to &dyn DynMyTrait
- convert the
&'a dyn DynMyTrait
to impl MyTrait + 'a
2021-10-27
- Syntax for named function types
- Plan around dynamic dispatch
- Stakeholder meeting, agenda plan?
- Next steps for dyn
with
clauses?
Late-bound and arguments
Stakeholder meeting
- Explain the plan
- Concept of MVP
- MVP
- Dyn async fn in traits
- using a proc macro to generate a wrapper struct
- Naming of output types
- Feedback in meeting
- How often do you think you will want dyn vs static dispatch?
- Feedback after trying it
Dyn async fn
High-level idea
2021-10-25
What we want:
Current status of GATs, doing an analysis of the trait def'n to find missing where clauses:
- For every GAT
G
in a trait definition with generic parameters X0...Xn
from the trait and Xn..Xm
on the GAT… (e.g., Item
or Iterable
, in the case of Iterable
, with generic parameters [Self]
from the trait and ['me]
from the GAT)
- If for every method in the trait… (e.g.,
iter
, in the case of Iterable
)
- When the method signature (argument types, return type, where clauses) references
G
like <P0 as Trait<P1..Pn>>::G<Pn..Pm>
(e.g., <Self as Iterable>::Iterator<'a>
, in the iter
method, where P0 = Self
and P1
= 'a
)…
- we can show that
Pi: Pj
for two parameters on the reference to G
(e.g., Self: 'a
, in our example)
- then the GAT must have
Xi: Xj
in its where clause list in the trait (e.g., Self: 'me
).
How we desugar RPIT today:
Tests
Things to add
- Generic async fn
- with where clauses req'd to type check
- Elided lifetimes in async fn
Impl notes
async fn
-> return position impl Trait
-> associated type
- Be careful about elided lifetimes
- May or may not be easier to start with a nameable associated type
gensym
?
- Ask petrochenkov how to do it
- Check what argument position impl trait does
2021-10-20
- RFC Draft
- tmandry to upload to repo
- and open a PR to rust-lang/rfcs
- Milestones
- Santiago to make a list of tests for desired behavior
- Tyler/Niko review list of tests and add anything that seems to be missing
- Santiago to write tests for desired behavior
- Naive desugaring for async fn in traits/impls
- literally introduce an associated type with the same name as the method but with
Output
appended?
- Niko to open RFC for
-> impl Trait
in traits
- Implement
-> impl Trait
in traits properly
- Implement the dyn procedural macro "stuff"
- Ergo-dyn milestones (dyno)
When would you want to use dyn anyway?
-
Parser combinator library
- every combinator returns
Box<dyn Parser>
or whatever
-
In async, passing around a dyn AsyncRead
or whatever
- In sync, passing around a
dyn Read
-
Things you lose access to:
- Associated constants
- Static methods, constructors
-
Kinds of traits:
- Traits where all methods are
&self
; ideally Trait
would be implemented for &Trait
- Traits where all methods are
&mut self
- Traits where methods are
self
- In general,
Trait
should be implemented for Box<Trait>
-
In general, Trait
should be implemented for Box<Trait>
- Properties of
Box
that we need?
Something Niko likes is that you can have an &dyn Debug
and call it without forcing any allocation
Homework:
- Try to write up some example programs and the ergonomic nits that result
2021-10-18
if you had that option, one could write a decorator to make it convenient
Named function types
impl trait in traits
Late-bound lifetimes
Argument types
Foo<..>
// means the fn type
let's call this U
<U as FnOnce<(A1, A2)>>::Output
Can we … think of impl Trait
as late-bound?
// given the fn foo from line 62
Rule: You need to name all the named type parameters, the rest are all late bound.
- Already the case that elided lifetime parameters are always late bound.
- For
impl Trait
, when it's called we're just doing FnOnce
trait selection with the concrete type of that argument. No need to have a different function type for each instantiation.
2021-10-13
with
with(x: T)
is a where clause
- dyn trait
- bounded clauses
in('a) { T: Trait }
- in the future, would be the default
in('_) { ... }
- "needs to be true independently of the scope I'm in "
Implemented(T: Trait for 'a)
- other use cases:
- implication types
P => T
with(tokio: &mut TokioRuntime) => Box<dyn xxx>
- other use cases:
for<'a, 'b> fn(&'a &'b u32, &'a u32, &'b u32)
- unsound because of https://github.com/rust-lang/rust/issues/25860
- could upcast
for<'a, 'b> fn(&'static &'static u32, &'a u32, &'b u32)
for<'a, 'b> ('b: 'a => fn(&'a &'b u32, &'a u32, &'b u32))
for<'a, 'b where 'b: 'a> fn...
- borrow checker integration
- I think what happens is that when you prove a predicate you get back a set of in-scope variables that are required
- much like today you get back lifetime bounds
- async live over yield question
- are
with
live over await? interesting question
can express both patterns
- optional / defaulted with clauses
- providing something that is
Copy
(e.g. shared ref) means can be used multiple times
Box<dyn Foo + 'a>
Box<dyn with(cx: &mut Context) => Foo>
trait WithContext = with(cx: &mutContext>
trait WithContext<predicate P> = with(cx: &mutContext> P
- complicaiton:
- how do you think about bounds on a struct type?
struct Foo<T: Debug>
- what is the bound here?
talking about dyn
2021-10-06
- What about threading local arena through?
- This design shouldn't add more complications to that
https://github.com/rust-lang/rfcs/pull/2492
2021-10-04
Niko muses about dyn
- given an instance
x: X
(let x = <Blah as Foo>::method
)
- trait
InstantiateFn<F> { ... }
x: InstantiateFn<fn(&Blah) -> XXX>
- Resolves to pointer to impl method
x: InstantiateFn<fn(&Blah) -> Box<dyn Iterator<Item = ...>>
- Resolves to pointer to wrapper fn around impl method
- given this, you can write generic code that generates a vtable
- has to run at compilation time: need const fn
- you can also write a
impl Foo for dyn Foo
that consumes the vtable
Impl question
Parent of a HIR node?
https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/hir/map/struct.Map.html#method.find_parent_node
Pieces
- MVP
- Ability to get type of a fn item
- fallback syntax to name items
- what about constants?
- for now we should make them an error to leave space for future expansion
- Ability to construct dyn vtables and implement dyn manually
- (Maybe) trait aliases
Next: Arenas
Sprint items
- Stakeholders
- MVP RFC
- needs work
- create a hackmd and write it together
- Evaluation for the output type naming (niko will call it impl trait)
- needs work
- sort of belongs in the impl trait initiative
- create a hackmd and write it together
2021-09-30
- Async stakeholders plan: link
- Possible stakeholders
- Web services: Niko has contacts in AWS
- Embedded Rust: Dario
- Web framework author:
- Web framework consumer:
- High-performance computing: glommio author
- Sourcing
- Dario
- Alice Rhyl
- Glauber Costa (glommio)
- farnz / Simon Farnsworth
- Android (BT?)
- Fuchsia (BT?) - Marie
- AWS people x 2 – agreed
- Yosh
- dyn for any trait
- do not have to specify the associated types (ever)
- maybe you can emit elide the other trait parameters
- but:
- you can only directly use members that meet the safety criteria for your dyn type
- they can't name associated types whose values you don't know
- they can't reference the generics whose values you don't know (including Self), at least not in contravariant positions
- and then:
- dyn trait Foo
- implements the Foo for dyn Foo impl in a particular way
- can give an error if that can't be done
- and you can give hints on methods (maybe?) to configure it a bit
- another part:
- what do we need to allow people to implement the dyn impl (unsafely) and what do we need (safely)
- goodies that extend what is dyn safe a little bit
- mapping
impl Trait
return types (or associated types) for dyn safe traits to Box<dyn>
and allocating
- impl trait in argument position ===> pass dyn by value? maybe Boxed?
impl trait in traits brainstorming doc
- for anonymous lifetimes, or if lifetimes are elided, always hrtb
- the same rule for gats
- probably
'_
should mean hrtb too
Niko's argment:
- '_ and impl Trait both should "introduce name into binder" at the same depth in all "input" positions
- in output positions, they "refer back" to a name from somewhere
'_
refers to self lifetime
-> impl Trait
returns to return type of body
- in where clauses:
- you don't want to introduce a fresh name at the item level because it would be unconstrained
- (this is less true for lifetimes for "reasons" but definitely true for types)
- so you probably want a forall at the where clause level
- exception: value of associated types
https://github.com/rust-lang/async-fundamentals-initiative/pull/3/files
https://lang-team.rust-lang.org/design_notes/fn_type_trait_impls.html
2021-09-28
Regarding the last bullet in "MvP":
2021-09-23
Niko would like it if traits had to be declared dyn
Why?
- Implementation soundness reasons
- Transparency / semver
- Don't accidentally make something not dyn safe
- It's clearer to users that something is happening
Plausible route:
- You have to supply the dyn impl
#[derive(dyn)]
to get the "default"
- this would be an edition change, obviously
Underlying mechanisms:
- Trait methods that return
impl Trait
:
- Only works for traits that
impl Trait for Box<dyn Trait>
- Vtable shim that boxes and creates
Box<dyn Trait>
- Controlling the kind of box (we have no idea how to do this yet, apart from
dyn(Box)
)
Why not return unsized values?
- Because futures have to have statically known size
Why not determine it from vtable?
Defining the underlying mechanisms
What Niko wants
- dyn is always 1 word
x as dyn Trait
requires that x
is a pointer (in practice)
box struct Foo
means that Foo
is actually Box<Foo>
(covenience)
dyn trait Foo
declare that a trait is "dyn-able"
dyn Trait: Trait
works kind of universally
fn foo(x: dyn Trait)
– you could pass in a &Foo
where Foo: Trait
(which has size one word)
fn foo(x: &dyn Trait)
is a pointer to pointer, not much we can do about that
2021-09-22
MVP
What works
What doesn't
Implementation work required
- Extend the parser
- Lower the thing in HIR lowering
- Improve error messages in some cases
- Make these traits not dyn-safe for now
Prepare a test suite
https://rust-lang.github.io/async-fundamentals-initiative/
https://github.com/rust-lang/async-fundamentals-initiative
https://rust-lang.github.io/async-fundamentals-initiative/evaluation/challenges/dyn_traits.html
Dyn traits
Normal "default impl" for dyn generated by the compiler
For async fn:
Auto traits and resulting futures
Basically we just need multiple impls:
Avoiding hard-coding box
"if you give me a box, I give you a box"
but is this what we really want? Then dyn foo doesn't quite work the same
"I tell you what I want, what I really really want"
Embedded workloads
- not depending on box
- inline
- smallbox
- maybe you have some allocator object?
Opening up dyn for Foo for anyone
rust```