or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing
xxxxxxxxxx
Fleshed out async fn sugar
Fn
traits and async are each complex in isolation. Syntax sugar exists for both of them to bridge this complexity gap and to draw syntactic symmetry between the sugar and how it relates to underlying concepts in the language.For
Fn
traits, we want to draw a parallel betweenfn
items and pointers, andFn
trait bounds via parenthesized argument lists and usage of the return arrow->
, and so we've settled on parenthesized generic sugar.For async, we want as much as possible for people to "just write
async
" in places that they want to be able to use.await
. This means:async
to theirfn function()
s in order to make them async.async
to their closures in order to make them async.async
to theirFn
trait bounds. <– this is the case that is under consideration in this meeting.We intentionally add
async
on top of the parenthesized generic sugar because that sugar is already well established: users are familiar with the parallel it draws between otherfn
-like concepts in the language.While it's useful to think about extensions towards a general
async
trait bound modifier, deciding to useasync
inFn
trait bounds does not require us to addasync
anywhere else – the parallel we're drawing here is betweenasync fn
andasync Fn
, not betweenasync Fn
and arbitraryasync Trait
s, and I believe this alone carries the weight of the feature.Risks:
async Fn
may draw users' attention towards the lackasync Trait
in general. I don't believe this is a concern, sinceFn
traits are special already. They are the only traits that allow parenthesized generic sugar; this specialness is a consequence of functions and closures being first-class in Rust, and we're leaning into users' familiarity with them as first-class language concepts in this proposal.There is a risk that
async Fn
might have different (user observable) semantics than some future async bound modifier. An argument could be made that by usingAsyncFn
as the trait name, we're saving space by mitigating any issues down the road if we choose a desugaring forasync Trait
that is inconsistent with the (user observable) details ofAsyncFn
. I don't believe that this is actually the case, though – inconsistency will still arise if we chose anasync Trait
desugaring that results inasync Fn
behaving differently thanAsyncFn
, so we're not saving any space by calling the traitAsyncFn
.Other considerations:
LendingFn*
compatibility - Using a first-class syntax allows more flexibility on the implementation side. This was argued at length by oli-obk in a thread on the RFC, but tl;dr is that trait aliases are not currently sufficient to move ontoLendingFn
in the future, so we'd have to add either new language support or a name-resolution hack to support this case. It's worth keeping this in mind, since not all ways of "saving space" are zero-cost.There is a risk of a trait bound naming blow-up if we ever gain
async gen
orgen
closures and settle onAsyncFn*
as the naming scheme. Extending these language features (e.g.async gen
closures) becomes less intuitive if users must learn thatAsyncGenFn()
is how it needs to be spelled in trait bounds.Discussion
Attendance
Meeting roles
General
Josh: They allow a special sugar, but they're still traits, with a trait name, and the sugar uses the trait name.
Josh: I appreciate the point about "just write
async
and it works." And I agree with that. But at the same time, I don't think that invalidates the point that this is introducing new sugar. This causes the bound to refer to a different trait than the one being used.CE: But from the user's point of view, it's not a different trait at all, since we're not exposing that. We're modifying the
Fn
trait.tmandry: There seem to be different ways to look at it.
Josh: There seem to be two things:
Fn
, but it isn't usingFn
.Josh: I'm feeling more flexible on point 1, and I feel like I wouldn't have a blocking objection to a different sugar. It's point 2 that's a sticking point for me.
Examples of sugars that seem fine:
async(A, B) -> R
,async fn(A, B) -> R
CE: This seems to put too much weight on the details of the
Fn*
traits themselves. These are special, and what users can see in terms of the implementation isn't something we guarantee.CE: The only user-visible thing that matters is the part of the signature that represents whether
Self
is taken by value, by reference, by shared reference, etc.Josh: Is there any reasonable path by which
Fn*
becomes the lending traits?CE: It could happen; we'd have to close a door that says we'd want to have pure lending fns. Each of the types would have to admit an implementation of
FnOnce
.Josh: An
async Fn
is not anFn
, in the literal trait impl sense: you cannot accept anasync Fn
and pass it to a thing expecting aFn
.CE: …
Josh: What you're saying is that people shouldn't focus on the details of the
Fn
traits and treat them as entirely compiler magic?CE: I think there's a middle ground. They are traits, but there's a lot of space we've reserved here. They are special, in parallel to that these are the only callable items within the language, and we have this callable syntax.
Josh: My feeling is that once we have variadic generics, we would start to stabilize the details of the
Fn*
traits.CE: My feeling is that these will always be a bit special, in the same way that
Drop
is kind of special. E.g., if we were to stabilize theFn*
traits today, I'd want to enforce that people can only implement only one of them.CE: So I'm further along the spectrum that the
Fn*
traits are special, and I want to lean into that a bit.Josh: That makes sense that these may be a bit special. I think the real problem I have here is that it names
Fn
when it's notFn
.Josh: If we committed to the path where the
Fn*
traits were the lending traits, then I'd be more OK with this path.CE: I don't necessarily buy that future argument. The worst case that I see is having a trait alias for
AsyncFn
…CE: I don't think this closes as big of a door as is being suggested here.
tmandry: I feel like we're running into the function coloring problem.
CE: The only aspect of the coloring that this makes obvious – and this happens regardless of what we name this – is the bit about passing
async Fn
to aFn
bound.tmandry: This is a difficult one, as it's both weaker and stronger.
CE: There are some orthogonal points here.
Josh: If we wanted to go forward with a general trait modifier syntax, I'm be substantially less concerned about this. Whether we end up with that or not, I don't feel like we have an obvious consensus at this time. That's a big part of this for me. I don't want this to be used as precedent to commit us to a general trait modifier syntax, and that sort of thing has happened before.
Josh: At the same time, I'm increasingly convinced by the arguments that we want a sugar of some kind. So we could write down the various things we've proposed as sugars.
CE: Please note that we need to select from sugars that are sufficiently expressive. Some of the proposals above (e.g.
async fn() -> T
) does not provide a way to express the self/&self/&mut self distinction that is the fundamental motivation forFnOnce
/Fn
/FnMut
.TC: Let's do it:
*
is short hand for empty string or one ofonce
,mut
. (Or capitalized when it appears as part of a trait name e.g.FnOnce
))async Fn*() -> T
AsyncFn*() -> T
async fn * () -> T
async * () -> T
async * |A, B| -> T
TC: Silly example I find motivating:
CE: Could we move this to an open question, the trait bound syntax?
Josh: I'd be OK with that. This is the only concern.
Josh: I am concerned with building inertia behind
async Fn
by leaving that working in nightly as the only option behind the feature gate. I'm OK with the RFC still sayingasync Fn
(with one mention of the unresolved question up front) as long as they get equal treatment e.g. in nightly and in a blog post.tmandry: Josh, is there anything you can see that would move your -1 to something else?
Josh: There are two things that would resolve that. I can't say I'd be thrilled with either happening. This all boils down to:
What does
async Ident
mean in type/bound context:async Trait
with some automatable transformation toTrait
, and it's consistent withasync Fn
, and we commit to doing that, then this becomes a -0.async Ident
might mean something else (including some transformation ofTrait
that isn't consistentasync Fn
), then this stays -1.async Ident
will never mean anything in either type or bound position (other than this special case), then I think this becomes -0.5.TC: Given your number 1 and number 3, taking the union of those, what if we commit that, either way, the semantics of
async Fn
would represent a fixed point in any design of number 1?Josh: That's almost true; what we'd also need to rule out is the possibility of using
async Ident
for something else, e.g. RFC 3628 or something like that.tmandry: That would be the use of the keyword in type position; this is use in trait position.
TC: Correct. There's no technical conflict here.
Josh: That's true, but I don't know that we can precommit to rejecting certain counterarguments (such as it being confusing to use
async Ident
for one thing in type position and a different thing in bound position).TC: As in, people would raise arguments of confusion.
tmandry: How would you feel about a future in which
async Trait
is a different trait fromTrait
?Josh: I treat that as another case of number 1. It would be consistent with
async Fn
. (I think some folks might have concerns with name resolution weirdness.)TC: Here's the path I'm seeing, in addition to raising the open questions here:
async Fn
as a fixed point in the design space of general async trait bound modifiers.async $ident
, e.g. in type position, e.g. RFC 3628, or that we could adopt a semantic for this that is compatible with async closures, e.g. TC's propose for async arrow syntax.Josh: Part of the reason I'm
-1
on theasync Fn
bound syntax is that I'm also-1
on acceptingasync Trait
as a general trait transformer mechanism or on rejectingasync $ident
entirely.TC: Other than things like RFC 3628, what other sort of possibilities should we consider here, in terms of deciding whether to close a door?
Josh: There aren't others that I can think of right now. But
async
is a pervasive part of the language and I think it's very likely to come up in other ways. I don't off-hand want to prereject other ideas we may come up with here.lower case sugar
async fn(A, B) -> C
=>async Fn(A, B) -> C
async fn mut(A, B) -> C
=>async FnMut(A, B) -> C
async fn once(A, B) -> C
=>async FnOnce(A, B) -> C
once
work as a contextual keyword here?(The main part of the meeting ended here.)
just async
async(A, B) -> C
=>async Fn(A, B) -> C
async mut(A, B) -> C
=>async FnMut(A, B) -> C
async once(A, B) -> C
=>async FnOnce(A, B) -> C
once
work as a contextual keyword here?eholk: Duality between
async -> T
as sugar forimpl Future<Output = T>
andasync(A, B) -> T
as sugar for an async fn?Josh: Or
async T
, yeah. Either way, there's a nice analogy.TC: Apropos of some things above, here's the async arrow proposal that had been earlier discussed.
I thought of a counterproposal to RFC 3628 that fits more naturally with async closures. Async arrow bounds and block type annotations:
The symmetry here is:
async Fn() -> T
is a function that returns a future that resolves toT
.async -> T
is a future that resolves toT
.This is similar to eholk's more general
impl Future -> T
proposal, but is shooting for this consistency with async closures, and takes advantage of the fact that we already have this keyword reserved.Unlike RFC 3628, I keep the
impl
because I think that's important for explaining how things work unless we go generally back to some form of bare trait syntax.As for the block syntax, since async blocks really are a kind of closure-like thing (e.g. one can
return
from them), it feels like this symmetry should hold:Josh: This creates a new mental model about
->
as "a thing that resolves to this".TC: That's an existing part of the mental model, e.g. in
async fn foo() -> T
the->
means "resolves to T".TC: Here's how I'd justify the
->
in the block syntax:Josh:
closure syntax
async * |A, B| -> T