I agree, as I don’t really “get” the chaining argument. Take this basic code:
let mut frob = Frob::new(Widget::open()?)?;
Is this idiomatic, or is it better to split it over two lines?
let widget = Widget::open("")?;
let mut frob = Frob::new(widget)?;
I find the second one much nicer. And if we agree on that, why the push for
let mut frob = Frob::new(Widget::open().await?).await?
Perhaps I have a limited view, but I think (non-stream) async calls as being of two kinds:
connect-style operations, like on a socket, HTTP connection, database, device discovery API, my
Widget::open() example and so on, where the result of the future enables access to new functionality
- transfer operations, like message reads or writes
For the first kind, it’s often desirable to reuse the “connection”. Even when that’s not necessary, it’s not a bad idea to avoid chaining unrelated operations like establishing a connection and transferring some data.
For the second kind, these are often “one-shot” (wait for a timer, or open a connection, send a message, maybe wait for a reply) or involve looping (accept a new connection, spawn a handler for it, start over). For the former, chaining isn’t bad (although it’s totally fine to avoid it), and it’s not applicable for the latter.
So I’ll argue that for run-of-the-mill code (proxies and gateways, CRUD/REST services), chaining is not a requirement, and often not desirable. And when it can be used, it’s often very limited; take a look at https://github.com/inejge/await-syntax/blob/postfix-field/bin/wlan/wlantool/src/main.rs and count the chains of three or more
awaits in a row.
This means that there is no need to introducing new syntax solely to support chaining.