Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Excited for the feature.

Disappointed we didn't get something like a magic method (e.g., lang item) (which isn't without precedence [1] [2]) so we could have had `Future::await(fut)` or `fut.await()`.

[1] - https://manishearth.github.io/blog/2017/01/10/rust-tidbits-b... [2] - https://manishearth.github.io/blog/2017/01/11/rust-tidbits-w...



`fut.await()` looks too magical. There's no hint in that expression that it's anything other than a method call. You could argue the same about `fut.await`, except in this case `await` is a keyword and thus the expression `x.await` is known to be an await expression without knowing anything about `x`, whereas `fut.await()` requires knowing the type of `fut` to understand that the method call `.await()` resolves to the lang item. Also, since it's a lang item, you could write your own nostd library that picks a different name for the same lang item and cause even more confusion.


If we're gonna have await share syntax with some other language feature, method syntax is IMO better than field syntax- it semantically blocks the function until the callee is complete, it evaluates to a temporary instead of an lvalue, it can unwind the caller (via cancelation, like a panic).

You could implement that syntax and still make `await` a keyword, without doing the usual method lookup and without letting no_std libraries change the term, just like `.await`- that's an orthogonal concern.


This just then boils down to saying "the await keyword should have required parens after it", which is a different argument than saying "it should be a lang item".


hell, I'd support that. A function/method 'does a thing' while field access 'reads a thing'. Rust doesn't have implicit getters and setters like Python might have. It doesn't LOOK like `fut.await` should/could DO anything, but it can.


Ah, but isn't the whole point of an `async` function that it syntactically erases the notion of blocking on an operation? If you ignore the fact that it blocks the async function (and yields control back to the executor), then `foo.await` isn't really "doing" anything at all, it's just handing you back the future's output.

Having said that, I'd have to assume that `fut.await` does consume the future, which normal field access doesn't do. Personally, I'm ok with the tradeoff that says "this keyword looks like field access but it consumes the receiver, but as a result we don't have these extra parens all over the place on a keyword", especially because going the other way introduces other costs (e.g. it looks like a method call and yet can't be referred to as Future::await).


It doesn't do anything locally but it does (as part of going back to the executor) let arbitrary other code run, which is basically what a method call does.


That's covered under the whole "erasing the notion of blocking on an operation".

And besides, conceptually that's no different than yielding back to the OS thread scheduler.


    impl Future {
        fn await(self: Self) -> Self::Target {
            intrinsic::await(self) # compiler magic
        }
    }

Then you CAN do `fut.await()` or `Future::await(fut)`, etc. Assuming this wouldn't be impossible for some other reason. And it makes it clear that `await` is magic, not something that a new programmer just can't find the definition of or assumes is the result of a macro or something.

> we don't have these extra parens all over the place on a keyword

like `fut.await().await().await()` vs. `fut.await.await.await`? Both look equally stupid and I feel like the existing Future APIs already discourage these kinds of things from happening by providing good APIs. But it's been a while since I played with them.


It's impossible for some other reason.

Namely, await isn't a matter of just inserting some compiler-defined behavior at a specific point. It requires rewriting the control flow of the surrounding function. The await! macro did this by invoking a magical unstable keyword `yield`, but even that still has to be done within the current function (e.g. the implementation of await! must be a macro, not a function).


I mean, that just means the compiler needs to ignore the `Future::await()` function scope and rewrite the context above. It's already being magical. A slight indirection in implementation would be worth the pedagogical clarity, I think.


> There's no hint in that expression that it's anything other than a method call.

Compared with `fut.await`...

> There's no hint in that expression that it's anything other than a field access.

At least there is precedence for functions to have magical compiler-ness for 'doing stuff', instead of overloading field access which is one of the simplest operations you can have.

> ... requires knowing the type of `fut` ...

tbh, you don't need to know how await works. It goes and does stuff and returns a value. If you need to know what it does, you're looking up the types and seeing, 'oh its a future' and looking at that.

Contrast with the field access one and you don't even know its something else entirely.

I guess I just don't buy that it can't be a method. Like, formally, yea, it isn't a well-defined, safe Rust method. But make the impl

    fn await(self: Self) -> Self::Target {
        intrinsic::await(self)
    }
Now its a method defined on the `Future` trait, it's discoverable, it's clearly magic, it's ergonomic... In my opinion, it beats the field access one in every conceivable metric.

It's just frustrating, I guess.


If it's a method you should be able to create a vector of futures and `.map(Future::await)` or even `.map(Future::await as FnMut(_) -> _)`. Clearly this would never work, since `await` is implement by transforming the containing function into a state machine, your suggestion would make this impossible.


I mean, why couldn't it?

    let newvec = vec![fut1, fut2, fut3].map(Future::await).collect()
would be equivalent to

    let f1 = fut1.await();
    let f2 = fut2.await();
    let f3 = fut3.await();
    let newvec = vec![f1,f2,f3];
or

    vec![fut1,fut2,fut3].map(|fut| fut.await).collect()


I think you fundamentally misunderstand async await. `vec[fut1, fut2, fut3].map(|fut| fut.await).collect()` will not do what you might expect, although I haven't bothered to read the RFC whether it will be allowed at all. Await must have compiler support and cannot just be some function you call.


I am certainly being sloppy, though I think fundamentally misunderstanding it is a bit of an exaggeration. I know you can't await in a non-async function so, at the very least, you'd need (IDR the syntax for async closures?)

    vec![fut1, fut2, fut3].map_async(async|fut| fut.await).collect()
I believe that could be built. Though, now I'm getting myself tripped up. Because I claimed that this is similar to

    vec![fut1, fut2, fut3].map(Future::await).collect()
But I notice that I expect `await` to be a blocking, non-async function. Which, feels strange in that the lambda in the first block needs to be async but the function itself doesn't.

> Await must have compiler support and cannot just be some function you call.

I'm not saying it doesn't need compiler support. I explicitly state that it does - I just want the magic to be a little more explicit. Moreover, I believe that most programmers think of functions as 'encapsulated code', something that does a thing. In this sense, it matches what await does better than syntax that looks like it's accessing a field. Admittedly, in the purest sense of encapsulation, this method 'escapes' that encapsulation, but I don't consider that terribly important.


> At least there is precedence for functions to have magical compiler-ness for 'doing stuff', instead of overloading field access which is one of the simplest operations you can have.

Except we don't actually have precedence for "I'm going to declare a function, but it's not actually implemented in Rust, instead it's a cue to the compiler to do other magical stuff". Lang items are in fact the opposite; they're telling the compiler "here's the implementation for an extension point you've defined". For example the "panic_impl" lang item is a function that's invoked when code panics. The closest I can think of to what you're referring to is the fact that the type identified as "owned_box" has special dereferencing behavior, but that can be modeled as the compiler providing an implementation for an un-namable trait on the owned_box item where the trait represents the new deref behavior (just as how Deref and DerefMut represent the existing deref behaviors).

> tbh, you don't need to know how await works.

You need to know the type in order to determine if `fut.await()` is invoking this magical semantics-altering compiler behavior or if it's just invoking a method that happens to be named `await` (given that the lang_item proposal means await is no longer a keyword).

> Contrast with the field access one and you don't even know its something else entirely.

Except you do, because await is a keyword. It's impossible in Rust 2018 to have a field named await, and so any expression that looks like `x.await` is guaranteed to be an invocation of the await behavior.

> I guess I just don't buy that it can't be a method.

It cannot be a method that is implemented as a call to an intrinsic. The await keyword requires rewriting the function that invokes it.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: