That's not a good assumption. Random people liking an idea is not "consensus". In fact I don't even see consensus in that thread, just discussions.
The only "consensus" seems to be that it's possible to add auto-deref in the future in a backwards compatible way, if it is ever added.
Also, the only consensus that matters is the consensus of the Rust Lang team. And at least one member of the Rust Lang team is against it (at least for the time being).
It's true that it's a bit abusive to use Deref in that way, nonetheless, field syntax can execute type-specific user code. And it can block.
Yes it was contrived, it was just the simplest example I could give. Some crates use Deref to do interesting (non-contrived) things. I make no comment on whether they should do these things, but it's certainly possible.
And as I explained in another post, a lot of popular languages have the ability to run custom code when accessing/setting a field. This is widely accepted and considered idiomatic! So the idea of field access being equivalent to a method is already quite common (just not in Rust).
Quite right. And the same is true for async / await.
Let me explain very clearly, step by step.
Let's look at this function:
async fn foo() -> i32 {
println!("Before");
bar().await;
println!("After");
return 5;
}
We're going to ignore all the implementation details about unsafe pointers and the state machine and all that. Let's just focus on what the code does, from the programmer's perspective.
First, the async fn gets transformed into fn foo() -> impl Future<Output = i32>. The mechanics of how this is done don't matter. The point is that it returns a Future.
So when you call foo(), it returns a Future, which you can then poll (polling is a part of the public API of Future):
let x = foo();
// Needed for pin safety, but irrelevant to this discussion.
pin_mut!(x);
// Gets a Context somehow. There's many ways to do this, depending on what you're trying to do. It's also irrelevant to this discussion.
let cx = ...;
let result = x.poll(&mut cx);
println!("Done");
What happens when it runs x.poll(&mut cx)? Well, first it prints Before, then it calls the bar() function, and then it awaits. This awaiting will check whether the bar() Future is ready or not.
If it's ready, it then continues with the rest of the foo() function (which prints After and returns 5). And so result is now Poll::Ready(5).
On the other hand, if it's not ready, then await immediately returns Poll::Pending, which means result is now Poll::Pending.
And lastly it prints Done. So, assuming bar() wasn't ready, that means it printed Before, and then Done (not After).
This is not what would happen if await was a blocking call: in that case it would wait for bar() and foo() to finish before printing, so it would always print Before -> After -> Done.
That means that the await really did return, and that return is visible from outside of the async fn.
It isn't just an implementation detail of the state machine. It's a part of the public API of Future, which the programmer can observe.
This is exactly the same as an fn which uses ?: the ? really will cause a real return, which is visible outside of the fn.
Does the programmer need to always be aware of this? No, most of the time they don't, but sometimes they do.
For example, it's important when understanding the interaction between async / await and multi-threaded or blocking code (and why it's bad to use blocking code inside of async).
I don't think that mental model is correct, because unlike languages like Python and JavaScript, Rust is multi-threaded.
So there really is a distinction between async/await and real threads, which you need to be aware of when dealing with multi-threaded code.
It's fine to have a more vague concept most of the time (ignoring details can be useful!), but sometimes the distinction really does matter.
I have no problem with people thinking of .await as a "suspension point", without any regard to the actual implementation of how it works. That's perfectly fine.
But your point was that field access cannot block, but method calls can block, and await is basically the same thing as blocking, so therefore it makes more sense as a method call.
But my point is that the await isn't actually blocking at all: it's immediately returning! It's actually the opposite of blocking. That's why it needs to use a complicated state machine transformation (unlike blocking method calls).
So I see no problem with await having weird syntax, since what it's doing is fundamentally weird.
Most of the time you can ignore that weirdness and pretend that it's like synchronous blocking, but there really is a difference, and sometimes that difference is important.
I agree, but I don't think that has anything to do with .await vs .await(). The state machine will be the same either way. People can get used to it either way. People need to be aware of it either way.
Absolutely, I just disagree with your claim that .await() will be significantly better in that regard than .await.
I think that after the initial shock has worn off and familiarity has worn in, people will make mistakes with either syntax. People will become complacent with either syntax. Adding a bit of syntactic salt with () won't necessarily make people more alert.
I could be wrong on that! But that's my opinion right now.
(As a meta note, I'm glad that we're able to have a civil debate about this, rather than it devolving into logical fallacies and ad hominems)