Hacker News new | ask | show | jobs
by color_me_not 836 days ago
I don't understand the comment in the method print_point in the class Point of the tutorial.

    [...]
    # This function is declared as `async` because it
    # awaits the result of print.
    async fn print_point(p) {
        # [...]
        print("The point is: {p}").await
    }

    [...]
From the first page of the tutorial:

> Dada, like JavaScript, is based exclusively on async-await. This means that operations that perform I/O, like print, don't execute immediately. Instead, they return a thunk, which is basically "code waiting to run" (but not running yet). The thunk doesn't execute until you await it by using the .await operation.

So, what it boils down to is that async/await are like lazily computed values (they work a bit like the lazy/force keywords in Ocaml for instance, though async seems to be reserved for function declarations). If that is the case, that method "print_point" is forcing the call to print to get that thunk evaluated. Yet, the method itself is marked async, which means that it would be lazily evaluated? Would it be the same to define it as:

    fn print_point(p) {
        print("The point is: {p}")
    }
If not, what is the meaning of the above? Or with various combinations of async/await in the signature & body? Are they ill-typed?

I wish they'd provide a more thorough explanation of what await/async means here.

Or maybe it is a dadaist[0] comment?

[0] https://en.wikipedia.org/wiki/Dada

2 comments

I think they didn't do a very good job explaining it. Await doesn't just mean "please run this thunk", it means "I am not going to deal with this thunk, can someone come and take over, just give me the result in the end".

What this means, concretely, in Rust, is `.await` will return the thunk to the caller, and the caller should resume the async function when the result is ready. Of course the caller can await again and push the responsibility further back.

The most important thing here, is that `.await` yields the control of execution. Why does this matter? Because IO can block. If control wasn't given up, IO will block the whole program; if it is, then something else will have a chance to run while you wait.

So, you mean that this thunk is produced by the async function, and the await keyword will run it asynchronously?

In other words, print produces a thunk, and print_point also produces a thunk, and when await is used on the later, it is executed asynchronously, which will execute the print also asynchronously. So we end up with 3 different execution context: the main one, a one for each "await"?

What is the point of this, as opposed to executing the thunk asynchronously right away? Also, how does one get the result?

> the await keyword will run it asynchronously?

From the point of view of print_point await executes the thunk synchronously, print_point execution stops and awaits for print to finish it work. But a callee of print_point might want it to run print_point asynchronously, so print_point is an async fn, and callee can do something more creative then to await.

Thank you.

So, it seems I had understood the principle the way you explain it, but the code comment on print_point (as I indicated in the top of this thread) isn't saying that.

I suspect they're heavily relying on intuition coming from Rust, where both of those forms are okay. The one from TFA is sugar for your version. This works fine as long as there is only a single await point, otherwise you have to transform the syntax into a bind, which you might not be able to legally do manually (in Rust at least) if you hold a borrow across the await point.