Closed cramertj closed 5 years ago
Another option could be using become
keyword instead of await
:
async fn become_chain() -> Result<usize, Error> {
let _ = partial_computation_future(become)
.unwrap_or_else(or_recover_future)
.start(become)?;
}
client.get("https://my_api")
.send_future(become)?
.json_future(become)?
let output = future.start(become);
become
could be a keyword for guaranteed TCO though
Thanks @EyeOfPython for that overview. That's especially useful for people just now joining the bike-shed.
Personally I'd hope we stay away from f await
, just because it's very un-rustic syntax wise and makes it seem kinda special and magic. It would become one of these things that users new to Rust are going to be confused about a lot and I feel it doesn't add that much clarity, even to Rust veterans, to be worth that.
@novacrazy the problem with this argument is that any
await!
macro would be magic, it's performing an operation that is not possible in user-written code
@Nemo157 I agree that an await!
macro would be magic, but I would argue that this is not a problem. There are multiple such macros in std
already, e.g. compile_error!
and I haven't seen anyone complain about them. I think it's normal to use a macro only understanding what it does, not how it does it.
I agree with previous commenters that postfix would be the most ergonomic, but I would rather start out with prefix-macro await!(expr)
and potentially transition to postfix-macro once that is a thing rather than have expr.await
(magic compiler-builtin field) or expr.await()
(magic compiler-builtin method). Both of those would introduce a completely new syntax purely for this feature, and IMO that just makes the language feel inconsistent.
@EyeOfPython Mind adding future(await)
to your lists and table? All the positives of your assessment of future.await()
seem to transfer without the weakness
Since some have argued that despite syntax highlighting, foo.await
looks too much like a field access, we could change the token .
to #
and instead write foo#await
. For example:
let foo = alpha()#await?
.beta#await
.some_other_stuff()#await?
.even_more_stuff()#await
.stuff_and_stuff();
To illustrate how GitHub would render this with syntax highlighting, let's replace await
with match
since they are of equal length:
let foo = alpha()#match?
.beta#match
.some_other_stuff()#match?
.even_more_stuff()#match
.stuff_and_stuff();
This seems both clear and ergonomic.
The rationale for #
rather some other token is not specific, but the token is quite visible which helps.
So, another concept: if future was reference:
async fn log_service(&self) -> T {
let service = self.myService.foo(); // Only construction
*self.logger.log("beginning service call");
let output = *service.exec(); // Actually wait for its result
*self.logger.log("foo executed with result {}.", output);
output
}
async fn try_log(message: String) -> Result<usize, Error> {
let logger = *acquire_lock();
// Very terse, construct the future, wait on it, branch on its result.
let length = (*logger.log_into(message))?;
*logger.timestamp();
Ok(length)
}
async fn await_chain() -> Result<usize, Error> {
*(*partial_computation()).unwrap_or_else(or_recover);
}
(*(*client.get("https://my_api").send())?.json())?
let output = *future;
This would be really ugly and inconsistent. Let at least disambiguate that syntax:
async fn log_service(&self) -> T {
let service = self.myService.foo(); // Only construction
$self.logger.log("beginning service call");
let output = $service.exec(); // Actually wait for its result
$self.logger.log("foo executed with result {}.", output);
output
}
async fn try_log(message: String) -> Result<usize, Error> {
let logger = $acquire_lock();
// Very terse, construct the future, wait on it, branch on its result.
let length = ($logger.log_into(message))?;
$logger.timestamp();
Ok(length)
}
async fn await_chain() -> Result<usize, Error> {
$($partial_computation()).unwrap_or_else(or_recover);
}
($($client.get("https://my_api").send())?.json())?
let output = $future;
Better, but still ugly (and yet worse it makes github's syntax highlighting). However, to deal with that let introduce ability to delay prefix operator:
async fn log_service(&self) -> T {
let service = self.myService.foo(); // Only construction
self.logger.$log("beginning service call");
let output = service.$exec(); // Actually wait for its result
self.logger.$log("foo executed with result {}.", output);
output
}
async fn try_log(message: String) -> Result<usize, Error> {
let logger = $acquire_lock();
// Very terse, construct the future, wait on it, branch on its result.
let length = logger.$log_into(message)?;
logger.$timestamp();
Ok(length)
}
async fn await_chain() -> Result<usize, Error> {
($partial_computation()).$unwrap_or_else(or_recover);
}
client.get("https://my_api").$send()?.$json()?
let output = $future;
This exactly what I want! Not only for await ($
) but as well for deref (*
) and negate (!
).
Some caveats in this syntax:
$
symbol cause problems here?.
delimited expression (await_chain
fn demonstrate that), is that confusing?@Centril
IMO # is too close to the raw literal syntax r#"A String with "quotes""#
IMO # is too close to the raw literal syntax
r#"A String with "quotes""#
It seems quite clear from the context what the difference is in this case.
@Centril It does, but the syntax is also really foreign to the style Rust uses IMHO. It does not resemble any existing syntax with a similar function.
@Laaas Neither did ?
when it was introduced. I would be happy to go with .await
; but others seem unhappy with that so I am trying to find something else that works (i.e. is clear, ergonomic, sufficiently easy to type, chainable, has good precedence) and foo#await
seems to satisfy all of that.
?
has a bunch of other good points though, while #await seems rather arbitrary. In any case, if you want something like .await
, why not .await!
?
@Centril That was the main rationale behind future(await)
, to avoid the field access while not having to add any extra operators or foreign syntax.
while #await seems rather arbitrary.
#
is arbitrary, yes -- is that make or break? When new syntax is invented at some point it has to be arbitrary because someone thought it looked good or made sense.
why not
.await!
?
That's equally arbitrary but I have no opposition to it.
@Centril That was the main rationale behind
future(await)
, to avoid the field access while not having to add any extra operators or foreign syntax.
Instead it looks like function application where you are passing await
to future
; that strikes me as more confusing than "field access".
Instead it looks like function application where you are passing await to future; that strikes me as more confusing than "field access".
To me that was part of the conciseness. Await is in many aspects like a function call (callee executes between you and your result), and passing a keyword to that function made it clear that this is a different sort of call. But I see how it could be confusing that the keyword is similar to any other argument. (Minus syntax highlighting and compiler error messages trying to reinforce that message. Futures can not be called any other way than being awaited and vice versa).
@Centril
Being arbitrary isn't a negative thing, but having resemblance to an existing syntax is a positive thing. .await!
isn't as arbitrary, since it's not entirely new syntax; after all, it's just a post-fix macro named await
.
To clarify / add to the point about new syntax specifically for await
, what I mean by that is introducing new syntax that's unlike the existing syntax. There is a lot of prefix keywords, some of which have been added after Rust 1.0 (e.g. union
), but AFAIK not a single postfix keyword (no matter if separated by a space, a dot, or something else). I also can't think of any other language that has postfix keywords.
IMHO the only postfix syntax that doesn't significantly increase Rust's strangeness is the postfix macro, becauses it mirrors method calls but can clearly be identified as a macro by anyone who has seen a Rust macro before.
I also liked the standard await!()
macro. It's just clear and simple.
I prefer a field-like access (or any other postfix) to co-exist as awaited
.
cloned()
method on iterators.And I also liked the @?
as ?
-like sugar.
This is personal, but I actually prefer the &?
combination, because @
tends to be too tall and undershoot the line, which is very distractible. &
is also tall (good) but doesn't undershoots (also good). Unfortunately &
already has a meaning, although it would always be followed by an ?
.. I guess?
Eg.
Lorem@?
.ipsum@?
.dolor()@?
.sit()@?
.amet@?
Lorem@?.ipsum@?.dolor()@?.sit()@?.amet@?
Lorem&?
.ipsum&?
.dolor()&?
.sit()&?
.amet&?
Lorem&?.ipsum&?.dolor()&?.sit()&?.amet&?
For me, the @
feels like a bloated character, distracts the reading flow. On the other hand, &?
feels pleasant, doesn't distracts (my) reading and has a nice top space between the &
and the ?
.
I personally think a simple await!
macro should be used. If post-fix macros get into the language, then the macro could simply be expanded, no?
@Laaas As much as I would like for there to be, there are no postfix macros in Rust yet, so they are new syntax. Also note that foo.await!.bar
and foo.await!().bar
aren't the same surface syntax. In the latter case there would be an actual postfix and builtin macro (which entails giving up on await
as a keyword).
@jplatte
There is a lot of prefix keywords, some of which have been added after Rust 1.0 (e.g.
union
),
union
is not a unary expression operator so it is irrelevant in this comparison. There are exactly 3 stable unary prefix operators in Rust (return
, break
, and continue
) and all of them are typed at !
.
Hmm... With @
my previous proposal looks slightly better:
client.get("https://my_api").@send()?.@json()?
let output = @future;
let foo = (@alpha())?
.@beta
.@some_other_stuff()?
.@even_more_stuff()
.stuff_and_stuff();
@Centril That is my point, since we do not have post-fix macros yet, it should simply be await!()
. Also I meant .await!()
FYI. I do not think await
needs to be a keyword, though you could reserve it if you find it problematic.
union
is not a unary expression operator so it is irrelevant in this comparison. There are exactly 3 stable unary prefix operators in Rust (return
,break
, andcontinue
) and all of them are typed at!
.
This still means that the prefix-keyword variant fits into the group of unary expression operators, even if typed differently. The postfix-keyword variant is a much larger divergence from existing syntax, one that is too large in relation to await
's importance in the language in my opinion.
Regarding prefix macro and possibility of transitioning to post-fix: await
needs to remain a keyword for this to work. The macro would then be some special lang item where it's allowed to use a keyword as the name without an extra r#
, and r#await!()
could but probably should not invoke the same macro. Other than that it seems like the most pragmatic solution to make it available.
That is, keep await
as a keyword but make await!
resolve to a lang-item macro.
@HeroicKatora why does it need to remain a keyword for it to work?
@Laaas Because if we want to keep the possibility of transitioning open, we need to remain open for future post-fix syntax that uses it as a keyword. Therefore, we need to keep the keyword reservation so that we don't need an edition break for the transition.
@Centril
Also note that foo.await!.bar and foo.await!().bar aren't the same surface syntax. In the latter case there would be an actual postfix and builtin macro (which entails giving up on await as a keyword).
Could this not be solved by making the keyword await
combined with !
resolve to an internal macro (which can not be defined through normal means)? Then it remains a keyword but resolves to a macro in otherwise macro syntax.
@HeroicKatora Why would await
in x.await!()
be a reserved keyword?
It would not be, but if we keep post-fix unresolved, it need not be the solution we arrive at in later discussions. If that were the unique agreed upon best possibility, then we should adopt this exact post-fix syntax in the first place.
This is another time that we come across something that works a lot better as a postfix operator. The big example of this is try!
which we eventually gave its own symbol ?
. However I think this is not the last time where a postfix operator is more optimal and we can not give everything its own special charachter. So I think we should at least not start with @
. It would be a lot better if we would have a way to do this kinds of things. That is why I support the postfix macro style .await!()
.
let x = foo().try!();
let y = bar().await!();
But for this to make sense postfix macros them self would have to be introduced. Therefore I think it would be best to start with a normal await!(foo)
macro syntax. We could later expand this to either foo.await!()
or even foo@
if we really feel this is important enough to warrant its own symbol.
That this macro would need a bit of magic is not new to std and to me is not a big problem.
As @jplatte put it:
@Nemo157 I agree that an
await!
macro would be magic, but I would argue that this is not a problem. There are multiple such macros instd
already, e.g.compile_error!
and I haven't seen anyone complain about them. I think it's normal to use a macro only understanding what it does, not how it does it.
A recurring issue I see discussed here is about chaining and how to use await in chaining expressions. Maybe there could be another solution ?
If we want not to use await for chaining and only use await for
assignments, we could have something like just replacing let with await :
await foo = future
Then for chaining we could imagine some kind of operation like await res = fut1 -> fut2
or await res = fut1 >>= fut2
.
The only case missing is waiting and returning the result, some shortcut
for await res = fut; res
.
This could be easily done with a plain await fut
I don’t think I’ve seen another proposal like this one (at least for the chaining), so dropping that here as I think it would be nice to use.
@HeroicKatora I've added fut(await)
to the list and ranked it according to my opinion.
If anyone feels like my scoring is off, please tell me!
Syntax-wise, I think .await!()
is clean and allows for chaining while not adding any oddities to the syntax.
However, if we ever get "real" postfix macros, it will be a little weird, because presumably .r#await!()
could be shadowed, while .await!()
couldn't.
I'm pretty strongly against the "postfix keyword" option (separated with a space like: foo() await?.bar() await?
), because I find the fact that the await
is joined to the following part of the expression, and not the part that it operates on. I would prefer pretty much any symbol other than whitespace here, and would even prefer prefix syntax over this despite it's disadvantages with long chains.
I think "obvious precedence" (with the await?
sugar) is clearly the best prefix option, as mandatory delimiters is a pain for the very common case of awaiting a single statement, and "useful precedence" is not at all intuitive, and thus confusing.
Syntax-wise, I think
.await!()
is clean and allows for chaining while not adding any oddities to the syntax.However, if we ever get "real" postfix macros, it will be a little weird, because presumably
.r#await!()
could be shadowed, while.await!()
couldn't.
If we get postfix macros and we use .await!()
then we can unreserve await
as a keyword and just make it a postfix macro. The implementation of this macro would still require a bit of magic of cource but it would be a real macro just like compiler_error!
is today.
@EyeOfPython Could you maybe explain in detail which changes you consider in forward-compatibility
? I'm unsure in which way fut@await
would rate higher than fut.await!()
and await { future }
lower than await!(future)
. The verbosity
column also seems a bit strange, some expressions are short but have lower rating, does it consider chained statements, etc. Everything else seems balanced and like the most well condensed extensive assessment so far.
After reading this discussion, it seems that lots of people want to add a normal await!()
macro and figure out the postfix version later. That is assuming we actually want a postfix version, which I'll take as true for the rest of this comment.
So I'd just like to poll the opinion of everyone here: Should we all agree on the await!(future)
syntax FOR NOW? The pros are that there's no parsing ambiguity with that syntax, and it's also a macro, so no changing the language's syntax to support this change. The cons are that it will look ugly for chaining, but that doesn't matter since this syntax can be easily replaced with a postfix version automatically.
Once we settle that down and get it implemented into the language, we can then continue the discussion for postfix await syntax with hopefully more mature experiences, ideas and possibly other compiler features.
@HeroicKatora I rated it higher as it could free await
to be used as ordinary identifier naturally and it would allow other postfix operators to be added, whereas I think fut.await!()
would better be if await
was reserved. However, I'm unsure if this is reasonable, it also seems definitely valid that fut.await!()
could be higher.
For await { future }
vs await!(future)
, the latter one keeps the option open to change to almost any of the other options, whereas the first one only really allows either of the await future
variants (as laid out in @withoutboats' blog entry). So I think it should definitely be fwd(await { future }) < fwd(await!(future))
.
As for the verbosity, you are correct, after splitting cases into subgroups I didn't re-evaluate the verbosity, which should be the most objective one of all.
I'll edit it to take your comments into account, thank you!
Stabilizing await!(future)
is about the worst possible option I can imagine:
await
which further means that future language design gets harder.try!(result)
which we deprecated (and which requires writing r#try!(result)
in Rust 2018).
await!(future)
is bad syntax we eventually mean to deprecate, this is willfully creating technical debt.try!(..)
is defined in Rust whereas await!(future)
cannot be and would instead be compiler magic.try!(..)
was not easy and took a toll socially. Going through that ordeal again doesn't seem appealing.await!(future)
is noisy; unlike await future
you need to write !( ... )
.Iterator
s. Similar to await { future }
and await future
, the await!(future)
syntax will make method chaining difficult and induce temporary let
bindings. This is bad for ergonomics and in my view for readability.@Centril I'd like to agree but there are some open questions. Are you sure about 1.
? If we make it 'magic' could we not make it even more magic by allowing this to refer to a macro without dropping it as a keyword?
I joined Rust too late to evaluate the social perspective of 2.
. Repeating a mistake doesn't sound appealing but as some code still was not converted from try!
it should be evident that it was a solution. That brings up the question of whether we intend to have async/await
as an incentive to migrate to edition 2018
or rather be patient and not repeat this.
Two other (imho) central features very much like 3.
: vec![]
and format!
/println!
. The former very much because there is no stable boxed construction afaik, the latter due to format string construction and not having dependently typed expressions. I think these comparisons also partially put 4.
into another perspective.
I oppose any syntax that doesn't read somewhat like english. IE, "await x" reads something like english. "x#!!@!&" does not. "x.await" reads tantalizingly like english, but it won't when x is a non-trivial line, like a member function call with long names, or a bunch of chained iterator methods, etc.
More specifically, I support "keyword x", where keyword is probably await
. I come from using both the C++ coroutines TS and unity's c# coroutines, which both use a syntax very similar to that. And after years of using them in production code, my belief is that knowing where your yield points are, at a glance, is absolutely critical. When you skim down the indent line if your function, you can pick out every co_await
/yield return
in a 200-line function in a matter of seconds, with no cognitive load.
The same is not true of the dot operator with await afterwards, or some other postfix "pile of symbols" syntax.
I believe that await
is a fundamental a control flow operation. It should be given the same level of respect as 'if
, while
, match
, and return
. Imagine if any of those were postfix operators - reading Rust code would be a nightmare. Like with my argument for await, as it is you can skim the indent line of any Rust function and immediately pick out all of the control flow. There are exceptions, but they're exceptions, and not something we should strive for.
I am agree with @ejmahler. We should not forget of other side of development - code review. File with source code is much more often being read then wrote, hence I thing it should be easier to read-and-understand then to write. Finding the yielding points is really important on code review. And I personally would vote for Useful precedence
.
I believe that this:
...
let response = await client.get("https://my_api").send()?;
let body: MyResponse = await response.into_json()?;
is easier for understanding rather then this:
...
let body: MyResponse = client.get("https://my_api").send().await?.into_json().await?;
@HeroicKatora
@Centril I'd like to agree but there are some open questions. Are you sure about
1.
? If we make it 'magic' could we not make it even more magic by allowing this to refer to a macro without dropping it as a keyword?
Technically? Possibly. However, there should be strong justification for special cases and in this case having magic upon magic doesn't seem justified.
That brings up the question of whether we intend to have
async/await
as an incentive to migrate to edition2018
or rather be patient and not repeat this.
I don't know if we ever said we intended for new module system, async
/await
, and try { .. }
to be incentives; but irrespective of our intent they are, and I think that's a good thing. We want people to eventually start using new language features to write better and more idiomatic libraries.
Two other (imho) central features very much like
3.
:vec![]
andformat!
/println!
. The former very much because there is no stable boxed construction afaik,
The former exists, and is written vec![1, 2, 3, ..]
, to mimic array literal expressions, e.g. [1, 2, 3, ..]
.
@ejmahler
"x.await" reads tantalizingly like english, but it won't when x is a non-trivial line, like a member function call with long names, or a bunch of chained iterator methods, etc.
What's wrong with a bunch of chained iterator methods? That's distinctly idiomatic Rust.
The rustfmt
tool will also format method chains on different lines so you get (again using match
to show the syntax highlighting):
let foo = alpha().match? // or `alpha() match?`, `alpha()#match?`, `alpha().match!()?`
.beta
.some_other_stuff().match?
.even_more_stuff().match
.stuff_and_stuff();
If you read .await
as "then await" it reads perfectly, at least to me.
And after years of using them in production code, my belief is that knowing where your yield points are, at a glance, is absolutely critical.
I don't see how postfix await
negates that, especially in the rustfmt
formatting above. Moreover, you can write:
let foo = alpha().match?;
let bar = foo.beta.some_other_stuff().match?;
let baz = bar..even_more_stuff().match;
let quux = baz.stuff_and_stuff();
if you fancy that.
in a 200-line function in a matter of seconds, with no cognitive load.
Without knowing too much about the particular function, it seems to me that 200 LOC probably violates the single responsibility principle and does too much. The solution is to make it do less and split it up. In fact, I think that is the most important thing for maintainability and readability.
I believe that
await
is a fundamental a control flow operation.
So is ?
. In fact, await
and ?
are both effectful control flow operations that say "extract value out of context". In other words, in the local context, you can imagine these operators having the type await : impl Future<Output = T> -> T
and ? : impl Try<Ok = T> -> T
.
There are exceptions, but they're exceptions, and not something we should strive for.
And the exception here is ?
?
@andreytkachenko
I am agree with @ejmahler. We should not forget of other side of development - code review. File with source code is much more often being read then wrote, hence I thing it should be easier to read-and-understand then to write.
The disagreement is around what would be best for readability and ergonomics.
is easier for understanding rather then this:
... let body: MyResponse = client.get("https://my_api").send().await?.into_json().await?;
This is not how it would be formatted; running it through rustfmt
gets you:
let body: MyResponse = client
.get("https://my_api")
.send()
.match?
.into_json()
.match?;
@ejmahler @andreytkachenko I agree with @Centril here, the biggest change (some may say improvement, I would not) you gain from prefix syntax is that users are incentivized to split their statements over multiple lines because everything else is unreadable. That is not Rust-y and usual formatting rules make up for this in post-fix syntax. I also consider the yield-point to be more obscure in prefix syntax because await
is not actually placed at the code point where you yield, rather opposed to it.
If you go this way, let's experiment for the sake of spelling it out, rather with await
as a replacement for let
in the spirit of @Keruspe 's idea to really enforce this. Without the other syntax extensions because they seem like a stretch.
await? response = client.get("https://my_api").send();
await? body: MyResponse = response.into_json();
But for none of these I see enough benefit to explain their composability loss and complications in the grammar.
Hm... is it desirable to have both a prefix and suffix form of await? Or just the suffix form?
Chaining method calls together is idiomatic specifically when talking about iterators and to a much lesser extent options, but aside from that, rust is an imperative language first and a functional language second.
I’m not arguing that a postfix is literally incomprehensible, I’m makin a cognitive load argument that hiding a top-tier control flow operation, carrying the same importance as ‘return’, increases cognitive load when compared to putting it as close to the beginning of the line - and I’m making this argument based on years of production experience.
On Sat, Jan 19, 2019 at 11:59 AM Mazdak Farrokhzad notifications@github.com wrote:
@HeroicKatora https://github.com/HeroicKatora
@Centril https://github.com/Centril I'd like to agree but there are some open questions. Are you sure about 1.? If we make it 'magic' could we not make it even more magic by allowing this to refer to a macro without dropping it as a keyword?
Technically? Possibly. However, there should be strong justification for special cases and in this case having magic upon magic doesn't seem justified.
That brings up the question of whether we intend to have async/await as an incentive to migrate to edition 2018 or rather be patient and not repeat this.
I don't know if we ever said we intended for new module system, async/ await, and try { .. } to be incentives; but irrespective of our intent they are, and I think that's a good thing. We want people to eventually start using new language features to write better and more idiomatic libraries.
Two other (imho) central features very much like 3.: vec![] and format!/ println!. The former very much because there is no stable boxed construction afaik,
The former exists, and is written vec![1, 2, 3, ..], to mimic array literal expressions, e.g. [1, 2, 3, ..].
@ejmahler https://github.com/ejmahler
"x.await" reads tantalizingly like english, but it won't when x is a non-trivial line, like a member function call with long names, or a bunch of chained iterator methods, etc.
What's wrong with a bunch of chained iterator methods? That's distinctly idiomatic Rust. The rustfmt tool will also format method chains on different lines so you get (again using match to show the syntax highlighting):
let foo = alpha().match? // or
alpha() match?
,alpha()#match?
,alpha().match!()?
.beta .some_other_stuff().match? .even_more_stuff().match .stuff_and_stuff();If you read .await as "then await" it reads perfectly, at least to me.
And after years of using them in production code, my belief is that knowing where your yield points are, at a glance, is absolutely critical.
I don't see how postfix await negates that, especially in the rustfmt formatting above. Moreover, you can write:
let foo = alpha().match?;let bar = foo.beta.some_other_stuff().match?;let baz = bar..even_more_stuff().match;let quux = baz.stuff_and_stuff();
if you fancy that.
in a 200-line function in a matter of seconds, with no cognitive load.
Without knowing too much about the particular function, it seems to me that 200 LOC probably violates the single responsibility principle and does too much. The solution is to make it do less and split it up. In fact, I think that is the most important thing for maintainability and readability.
I believe that await is a fundamental a control flow operation.
So is ?. In fact, await and ? are both effectful control flow operations that say "extract value out of context". In other words, in the local context, you can imagine these operators having the type await : impl Future
There are exceptions, but they're exceptions, and not something we should strive for.
And the exception here is ? ?
@andreytkachenko https://github.com/andreytkachenko
I am agree with @ejmahler https://github.com/ejmahler. We should not forget of other side of development - code review. File with source code is much more often being read then wrote, hence I thing it should be easier to read-and-understand then to write.
The disagreement is around what would be best for readability and ergonomics.
is easier for understanding rather then this:
...let body: MyResponse = client.get("https://my_api").send().await?.into_json().await?;
This is not how it would be formatted; the idiomatic rustfmt formatting is:
let body: MyResponse = client .get("https://my_api") .send() .match? .into_json() .match?;
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/rust-lang/rust/issues/57640#issuecomment-455810497, or mute the thread https://github.com/notifications/unsubscribe-auth/ABGmeocFJpPKaypvQHo9LpAniGOUFrmzks5vE3kXgaJpZM4aBlba .
@ejmahler We disagree re. "hiding"; the same arguments were made wrt. the ?
operator:
The
?
operator is very short, and has been criticised in the past to "hide" a return. Most times code is read, not written. Renaming it to?!
will make it two times as long and therefore harder to overlook.
Nevertheless, we ultimately stabilized ?
and since then I think the prophecy has failed to materialize.
To me, postfix await
follows natural reading order (at least for left-to-right language speakers). In particular, it follows data-flow order.
Not to mention syntax highlighting: anything await-related can be highlighted with a bright color, so they can be found at a glance. So even if we had a symbol instead of the actual await
word, it would still be very readable and findable in syntax highlighted code. With that said, I still prefer the use of the await
word just for grepping reasons - it's easier to grep the code for anything that's being awaited if we only use the await
word instead of a symbol like @ or #, whose meaning is grammar dependent.
y'all this isn't rocket science
let body: MyResponse = client.get("https://my_api").send()...?.into_json()...?;
postfix ...
is extremely readable, hard to miss at a glance and super intuitive since you naturally read it as the code kind of trailing off while it waits for the result of the future to become available. no precedence/macro shenanigans necessary and no extra line noise from unfamiliar sigils, since everybody has seen ellipses before.
(apologies to @solson)
@ben0x539 Does that mean I can access a member of my result like future()....start
? Or await a range result like so range().....
? And how exactly to you mean no precedence/macro shenanigans necessary and
since currently ellipsis ..
requires being a binary operator or paranthesis on the right and this is awfully close at a glance.
Before commenting in this thread, please check https://github.com/rust-lang/rust/issues/50547 and try to check that you're not duplicating arguments that have already been made there.
Notes from shepherds:
If you're new to this thread, consider starting from https://github.com/rust-lang/rust/issues/57640#issuecomment-456617889, which was followed by three great summary comments, the latest of which were https://github.com/rust-lang/rust/issues/57640#issuecomment-457101180. (Thanks, @traviscross!)