Closed nikomatsakis closed 3 weeks ago
I have some concerns about introducing 🏠K<$ty>.
To understand a flavor is to understand its output type. I can't really explain a try block without telling you about Result, or gen without Iterator, or async without Future. This is demonstrated in the "teaching" section of the RFC where you define what a future is in the first few sentences of teaching async functions. So I don't think that hiding away that type with another syntax helps with learnability of the feature, because learning that specific type is critically important to understanding the flavor. If anything, naming that type in the code is helpful.
For devs of any experience level, how are we going to pronounce async\<T>? I'd have to say "a future of T". The syntax would just necessitate some mental translation to read it.
Is there even one other possible flavor where that syntax might work?
All the other parts of the language that can be made consistent makes sense to me, but the types really are special and should not be over-abstracted IMO.
One thing that may not be immediately apparent is that the stabilization of RFC 3668 async closures had become blocked on this RFC.
For that feature, we have to choose, as the bounds syntax, between async Fn{,Mut,Once}
and AsyncFn{,Mut,Once}
. The RFC had left this as an open question.
We had agreed, in later lang discussion, that we might have consensus on async Fn
as that syntax if and only if we had agreement that we would generalize this somehow to other traits. This RFC is the outcome of that.
On the plus side, there are many valuable bits in this RFC, and I hope that we are able to make something of those later. I'm glad that @nikomatsakis wrote this up, and working through this has I think sharpened all of our thinking. The resulting discussion on this thread and in Zulip has been particularly illuminating, and many people have made a number of compelling points.
But overall, I've come to believe that this RFC is trying to do too much and is foreshadowing too much when we have too many question marks. It's proposing a big model, and I'm just not sure that we've really nailed it.
As one concrete matter, while I was supportive of async Fn
, I've realized more clearly that my reasons for that do not necessarily generalize to other traits. The Fn
traits are special in many ways, and in fact, this specialness is what had originally prompted @compiler-errors to suggest that the async Fn
syntax could be adopted without having to decide generally about async
traits at all.
As has been discussed on this thread, where things get particularly difficult is when the trait that would be affected represents the state machine for some other "K". I remain strongly skeptical that we would ever want to have K OtherKTrait
in these instances.
Looking beyond that, I sense that it's harder to apply this K Trait
idea whenever the trait represents any kind of nontrivial finite state machine, and I suspect that is part of why we have so many question marks about the combination of this with traits like Read
.
At the same time, I believe that we will eventually have a first class "K-style" notion in the language. I'm just not confident that notion necessarily implies K Trait
. For traits, it seems at least possible that we will just have a handful of special cases (e.g. for Fn
, Default
, Clone
, etc.) or that we may have other available approaches for solving the relevant problems.
In that light, I particularly want to decouple async closures from this RFC and allow those to move forward. Personally, I've warmed to shipping async closures with AsyncFn{,Mut,Once}
bounds syntax, and I know from discussion that some other members of the team feel positively about this also. The author of RFC 3668, @compiler-errors, has also warmed to this.
In support of this, @nikomatsakis has pointed out in discussion that, since we wouldn't be soon shipping any kind of generalized form that would allow the ecosystem to write async Foo
, we are and will continue seeing AsyncFoo
traits appear regardless. And that if we did later come up with some generalized mechanism, we could at that time manage the transition from AsyncFn
to async Fn
.
I agree with and am persuaded by that also. I believe it's likely at this point that we will decouple async closures from this RFC and proceed to propose they be stabilized using the AsyncFn
bounds syntax.
As mentioned, there are many bits of this RFC that I do find valuable, and I hope that we do come back separately to these and are able to benefit from the careful thinking that went into this document and into the discussions that have been prompted by it.
From the meeting notes:
I do find the idea that you can import one name and add flavors to it later quite compelling.
Note that needing to import a trait manually helps avoid new ambiguities being added to code due to changes in other crates. Opting out of that seems like it could cause problems.
I find it kind of annoying that you have to go import TryFrom everytime you want to use that instead of regular From, for example.
If I'm understanding correctly, under this proposal flavored traits are supposed to have the same members as the base trait, meaning try From
would have a from
method instead of try_from
; so for any struct that implements both From and try From, I think you'd have to disambiguate which trait you were using every time? That seems much worse than needing to import TryFrom.
So, building on what @traviscross said, I'm inclined to close the RFC. I definitely feel disappointed, as writing the RFC helped clarify many things and I liked the idea of reducing the uncertainty a bit. But I also think it will be just fine to stabilize AsyncFn
and continue our discussions.
Like TC, I ultimately do believe we should have some form of K
-generics -- and I mean that in a fuller sense than this RFC, I think it's important that we are able to write combinator-like APIs (e.g., Iterator
) that work whether or not you have ?
, await
, const
, etc. Unlike TC, I am 100% confident that part of this will be "K-flavored" traits.
However, I don't think we need to stabilize async Fn
syntax now to get there, and I would really like to see async Fn
stabilized ASAP. I also feel that we should not use async Fn
unless we are prepared to apply the async
keyword to other traits, it will just be confusing for no reason. It seems better to start with AsyncFn
-- if we want, we can always make that a (deprecated) trait alias in the future.
I don't think this (discussion about more general "K-flavoring") really changes anything about the idea that the Fn
trait sugar was designed to match fn
item syntax.
More specifically: regardless of whether we start with AsyncFn
or async Fn
, we are extending that sugar. I would thus argue for async Fn
over AsyncFn
even if we never added async
to any other traits. And this line of discussion was already covered pretty heavily in the async closures RFC.
I think this is the right choice - it will be much easier to discuss this idea once we have a more concrete idea of how the syntax would be used in practice.
Summary
This RFC unblock the stabilization of async closures by committing to
K $Trait
(whereK
is some keyword likeasync
orconst
) as a pattern that we will use going forward to define a "K-variant ofTrait
". This commitment is made as part of committing to a larger syntactic design pattern called the flavor pattern. The flavor pattern is "advisory". It details all the parts that a "flavor-like keyword" should have and suggests specific syntax that should be used, but it is not itself a language feature.In the flavor pattern, each flavor is tied to a specific keyword
K
. Flavors share the "infectious property": code with flavorK
interacts naturally with other code with flavorK
but only interacts in limited ways with code without the flavorK
. Every flavor keywordK
should support at least the following:K
-functions using the syntaxK fn $name() -> $ty
;K
-blocks using the syntaxK { $expr }
(and potentiallyK move { $expr }
);K
-traits using the syntaxK $Trait
;K
-flavored traits should offer at least the same methods, associated types, and other trait items as the unflavored trait (there may be additional items specific to theK
-flavor). Some of the items will beK
-flavored, but not necessarily all of them.K
-closures using the syntaxK [move] |$args| $expr
to define a K-closure;K Fn
traits.Some flavors rewrite code so that it executes differently (e.g.,
async
). These are called rewrite flavors. Each such flavor should have the following:🏠K<$ty>
defining theK
-type, the type that results from aK
-block,K
-function, orK
-closure whose body has type$ty
.🏠K<$ty>
is a placeholder. We expect a future RFC to define the actual syntax. (The 🏠 emoji is meant to symbolize a "bikeshed".)cK
-block, consumes a🏠K<$ty>
and produces a$ty
value.K
-function can be transformed to a regular function with aK
-flavored return type and body.K fn $name($args) -> $ty { $expr }
fn $name($args) -> 🏠K<$ty> { K { $expr } }
Binding recommendations
Existing flavor-like keywords in the language do not have all of these parts. The RFC therefore includes a limited set of binding recommendations that brings them closer to conformance:
K $Trait
as the syntax for applying flavors to traits, with theasync Fn
,async FnMut
, andasync FnOnce
traits being the only current usable example.🏠async<$ty>
that will meet the equivalences described in this RFC.Not part of this RFC
The Future Possibilities discusses other changes we could make to make existing and planned flavors fit the pattern better. Examples of things that this RFC does NOT specify (but which early readers thought it might):
async
can be used with traits beyond theFn
traits:Read
trait, it will be referred to asasync Read
, but the RFC does not specify whether to add such a trait nor how such a trait would be defined or what its contents would be.const Trait
ought to work (under active exploration):const
-flavored trait should beconst Trait
; it does not specify what aconst
-flavored trait would mean or when that syntax can be used.🏠K<$ty>
:async
, but the precise syntax still needs to be pinned down. RFC #3628 contains one possibility.Rendered