# tradeoffs of postfix dot notations I've been reading this thread. I want to try and leave a few thoughts. I'm going to write two comments in short succession. This first one aims to give a more detailed look at the various "postfix options" that are based on `.`. It begins by summarizing the key points around each option, and then gives me "personal narrative" that explains where I personally fall. I want to emphasize that I am explicitly **not** considering future generalizations in this post. I'll talk a bit about the reasoning in the next post, but the bottom line is that "you never know what may come", so I'd like to reason about "what would happen if we had this syntax forever". (But also, in the next post, with an eye towards future possibilities.) Also, I apologize for the length of this post. If you prefer, [it is also available on HackMd](https://hackmd.io/s/rk9w1Nm3N). ### Go forth and experiment! One thing I want to emphasize is that I think we are could use fewer comments in this thread and more **lived experience**. We've seen a lot of fragments of code, but those fragments were written using `await!()`. **Now that `.await` syntax has landed, I would really like to see people go ahead and use it!** Give yourself a little time to get used to it, but also pay attention to moments of confusion or surprise. If you can give concrete examples, that'd be helpful. This is the point where `.await` will feel **the worst**, since we don't yet have syntax highlighting etc, so that should be a good torture test. I'd be quite interested to hear those reports. Finally, people have brought up screen readers and accessibility. I do not understand how `foo.await` might be a problem there, given that we already have field and method syntax, but perhaps others can clarify that (I'd particularly like to hear from people who are actually using screen readers). ### Feedback welcome As always, I'm interested in getting feedback -- but of a particular kind. I am looking for arguments (pros/cons) that you think are not included in this summary. If I see anything that seems new, I will add it to the summary -- and, hey, maybe you'll change my mind. =) NB: I will update the [HackMd version](https://hackmd.io/s/rk9w1Nm3N) of this post, not this comment. ### Executive summary - `foo.await` - **Pro**: very lightweight - **Con**: easily confused for a field access, which conveys a "no side effects" intuition - `foo.await()` - **Pro**: good analogy to blocking I/O; functions mean "something happens here" - **Con**: could mislead into thinking other methods could not block - **Con**: sort of strange to have a keyword with extra characters `()` "just because" - `foo.await!()` - **Pro**: macros have always had ability to do surprising control flow - **Con**: very verbose -- `foo.await!()?` - **Con**: not a macro, could never be - `foo.await!` - **Pro**: fairly lightweight, clearly not a field - **Con**: could mislead into thinking `!` is an operator (indeed, it *is*, for macros) - **Con**: no precedent for this sort of thing in Rust "syntactic tradition" - **Amusing**: means `foo.await!?` is a thing, for better or worse - But note that `foo.await?!` would *not* work -- I actually made this typo a few times, this could be pretty annoying in practice. =) ### Running example To help make each syntax more real, I want to use this "running example" (expressed in naive prefix form). These 'snippets' are adapted from [the await syntax repo](https://github.com/inejge/await-syntax/): ```rust let ids = (await interface_ids(service.clone(), interface_id))?; let response = (await wlan.list_interfaces()).context("error listing ifaces")?; ``` ### `.await` ```rust let ids = interface_ids(service.clone(), interface_id).await?; let response = wlan.list_interfaces().await.context("error listing ifaces")?; ``` The main concern about `foo.await`, from what I can tell, boils down to the fact that it will be confused for a field. This seems to offer the **biggest possible** "mismatch" between user expectations of the available options. In general, there is a sort of "spectrum" of side-effects one might consider from a given bit of syntax, and field accesses fall on the lowest possible side of that. Thus, making `foo.await` potentially block the current task and switch to another is potentially very surprising. (This is also an argument against progammable properties, which of course Rust doesn't have.) In Rust in particular, the "field vs operator" distinction can be significant in other ways. Method calls return temporaries, for example, so while `&foo.bar` returns a value that lives as long as `foo`, `&foo.bar()` creates a temporar that lives as long as the enclosing statement (typically). `&foo.await` would be the latter, which is unfortunate. Clearly, syntax highlighting will help to avoid confusion. I think this is very important, but I can see counter arguments: e.g., there are contexts where it doesn't apply: for example, when I write `foo.await` in markdown, I don't get highlighting. Similarly, syntax highlighters often highlight keywords in incorrect contexts or miss things, so maybe users don't trust this entirely. It is worth asking how much trouble there will **actually** be a result of this confusion. I suspect that, most of the time, `.await`'ing a future will not, in fact, have visible side-effects on the data you have in hand, although it clearly can. (In general, one of the big advantages of explicit `await` is that it lets you ignore async I/O most of the time, but **when you need to care, you can**. That is, when you need to audit for where side-effects occur, you can easily find them. This is similar to how `?` lets you ignore errors a lot of the time, while making them visible. Field syntax doesn't make it harder to audit, but it probably does cause those awaits to fade even further into the background than they otherwise might.) ### `.await()` ```rust let ids = interface_ids(service.clone(), interface_id).await()?; let response = wlan.list_interfaces().await().context("error listing ifaces")?; ``` One obvious answer to "fields accesses don't have side effects" is to use a syntax that conveys the notion of side-effects. We could, for example, use `await()`. There is some danger here, though: this is not an ordinary method. It is an operator, despite *looking* like a method. It could lead to the "inverse" confusion -- that is, that **any** method call may potentially block, instead of only this "special method" called `await`. Similarly, writing `.await()` as a function may make people "think of it" as a method, and thus be surprised that they can't use (e.g.) some fully qualified syntax like `Await::await(foo)` to invoke it without dot notation. Ultimately, I think that the `.await()` syntax is leaning very hard on the "you can think of async I/O as if it were blocking" intution very hard. In other words, you can think of an async fn as running in its own thread, with `.await()` corresponding to a blocking operation where the scheduler may choose to run another thread. In that case, the analogy is basically perfect, even though the model is not at all what is happening. (I find that somewhat appealing, myself, but you can also see where it might be misleading.) I personally find `await()` a bit surprising for other reasons: Rust has a kind of tradition of "bare keywords", and somehow adding two extra parentheses "just because" feels surprising to me. But I think that's a somewhat weak argument. (We do have `unsafe { }` blocks, which are *somewhat* similar.) ### `.await!()`? ```rust let ids = interface_ids(service.clone(), interface_id).await!()?; let response = wlan.list_interfaces().await!().context("error listing ifaces")?; ``` Certainly macros are expected to mean "this expands to unconventional things" -- so we could imagine using `!()` to convey that in this case. I think one very strong argument against this is just verbosity. Even with just `.await`, async I/O is already far less ergonomic than sync I/O -- adding `!()` feels to me like a bridge too far. Of course, we do use macros for a number of common purposes, such as `println!`, `panic!` and so forth. Indeed, those used to be keywords, but were made into macros to simplify the language and compiler. But here we don't get that benefit: async/await is still a core language concept; it also must still be implemented in the compiler. Separately, of course, it'd be nice to have general method macros at some point, but I don't see that as really affecting this question very much. If we choose to add method macros, it would not conflict with the existing of a `.await` keyword in particular. ### `.await!`? ```rust let ids = interface_ids(service.clone(), interface_id).await!?; let response = wlan.list_interfaces().await!.context("error listing ifaces")?; ``` One answer to the concerns with `!()` question is to just use `!`. This builds on the "`!` means this may have unconventional control flow" intution, but without being overwhelming or looking like a "normal" macro. It does mean that you would commonly see stuff like `let x = foo.await!?`, which is .. kind of amusing. Still, it's worth noting that `foo.await?!` would *not* work (it's meaningless) -- but normally when one combines `?` and `!` it is done like so "What on earth?!!!"". My fingers at least mistyped this quite a few times. =) Still, while Ruby and Lisp have identifiers like `foo?` and `bar!`, there isn't much precedent for this in Rust, where `!` is used a an operator (though in ways that don't, I don't think, conflict with this syntax): - `!foo` of course; - but also `$a!(..)` in a macro, which invokes whatever macro name `$a` expands to. ### My personal narrative The previous sections were meant to be fairly dispassionate (if not truly "objective"). This section tries to explain why I personally lean towards `foo.await` (for the purposes of this comment, I am ignoring future compatibility). I think the bottom line for me is that `foo.await` is the least intrusive syntax. When writing Async I/O code, I imagine one has to do a lot of awaiting, and most of the time you don't want to think about it very much. I think people will learn quickly that it is, indeed, *not* a field, but rather an operator, and I think that syntax highlighting will help. I am also influenced by the fact that @cramertj, who has written a **ton** of actual async-await code in Rust, prefers `.await`. I think that of the other options, I would choose `foo.await()` as a second choice. I don't mind that it "looks like a method", because I think that -- in terms of its potential affects -- `await` can be thought of as a "blocking method". My main hesitation here is that, ultimately, a keyword suffices to remove ambiguity, so the the `()` is just there to "signal" to the user that this is not a field. But maybe that's important enough to be worthwhile. (And, of course, there is no future generalization path here, but I'm trying to ignore that.) I could live with `foo.await!` because it's short, but ultimately it feels like more of a departure from our syntax to me. I personally find `foo.await!()` to be too verbose and do not consider it a contender. I realize others disagree. ### Feedback welcome Let me just repeat what I said before: As always, I'm interested in getting feedback -- but of a particular kind. I am looking for arguments (pros/cons) that you think are not included in this summary. If I see anything that seems new, I will add it to the summary -- and, hey, maybe you'll change my mind. =) NB: I will update the [HackMd version](https://hackmd.io/s/rk9w1Nm3N) of this post, not this comment.