Live data from Hacker News

Zig's new plan for asynchronous programs

lwn.net

81–90 of 274 posts

Re: Zig's new plan for asynchronous programs

#81
post #47

Earlier quoted context omitted.

C# works like this as well, no? In fact C# can (will?) run the async function on the calling thread until a yield is hit.

So do Python and Javascript. I think most languages with async/await also support noop-ing the yield if the future is already resolved. It’s only when you create a new task/promise that stuff is guaranteed to get scheduled instead of possibly running immediately.

I can't quite parse what you're saying.

Python works like this:

    import asyncio

    async def sleepy() -> None:
        print('Sleepy started')
        await asyncio.sleep(0.25)
        print('Sleepy resumed once')
        await asyncio.sleep(0.25)
        print('Sleepy resumed and is done!')


    async def main():
        sleepy_future = sleepy()
        print('Started a sleepy')

        await asyncio.sleep(2)
        print('Main woke back up.  Time to await the sleepy.')

        await sleepy_future

    if __name__ == "__main__":
        asyncio.run(main())
Running it does this:

    $ python3 ./silly_async.py
    Started a sleepy
    Main woke back up.  Time to await the sleepy.
    Sleepy started
    Sleepy resumed once
    Sleepy resumed and is done!
So there mere act of creating a coroutine does not cause the runtime to run it. But if you explicitly create a task, it does get run:

    import asyncio

    async def sleepy() -> None:
        print('Sleepy started')
        await asyncio.sleep(0.25)
        print('Sleepy resumed once')
        await asyncio.sleep(0.25)
        print('Sleepy resumed and is done!')


    async def main():
        sleepy_future = sleepy()
        print('Started a sleepy')

        sleepy_task = asyncio.create_task(sleepy_future)
        print('The sleepy future is now in a task')

        await asyncio.sleep(2)
        print('Main woke back up.  Time to await the task.')

        await sleepy_task

    if __name__ == "__main__":
        asyncio.run(main())

    $ python3 ./silly_async.py
    Started a sleepy
    The sleepy future is now in a task
    Sleepy started
    Sleepy resumed once
    Sleepy resumed and is done!
    Main woke back up.  Time to await the task.
I personally like the behavior of coroutines not running unless you tell them to run -- it makes it easier to reason about what code runs when. But I do not particularly like the way that Python obscures the difference between a future-like thing that is a coroutine and a future-like thing that is a task.

Re: Zig's new plan for asynchronous programs

#82
post #80
post #71

Earlier quoted context omitted.

yes, you can: runtime.block_on(async { }) https://play.rust-lang.org/?version=stable&mode=debug&editio...

Let me rephrase, you can't call it like any other function. In Zig, a function that does IO can be called the same way whether or not it performs async operations or not. And if those async operations don't need concurrency (which Zig expresses separately to asynchronicity), then they'll run equally well on a sync Io runtime.

> In Zig, a function that does IO can be called the same way whether or not it performs async operations or not.

no, you can't, you need to pass a IO parameter

Re: Zig's new plan for asynchronous programs

#83
post #71

Earlier quoted context omitted.

yes, you can: runtime.block_on(async { }) https://play.rust-lang.org/?version=stable&mode=debug&editio...

Here's a problem with that: Cannot start a runtime from within a runtime. This happens because a function (like `block_on`) attempted to block the current thread while the thread is being used to drive asynchronous tasks. https://play.rust-lang.org/?version=stable&mode=debug&editio...

just pass around handles like you do in zig, alright?

also: spawn_blocking for blocking code

Re: Zig's new plan for asynchronous programs

#84

I think this design is very reasonable. However, I find Zig's explanation of it pretty confusing: they've taken pains to emphasize that it solves the function coloring problem, which it doesn't: it pushes I/O into an effect type, which essentially behaves as a token that callers need to retain. This is a form of coloring, albeit one that's much more ergonomic. (To my understanding this is pretty similar to how Go sol…

If calling the same function with a different argument would be considered 'function coloring', every function in a program is 'colored' and the word loses its meaning ;) Zig actually also had solved the coloring problem in the old and abandondend async-await solution because the compiler simply stamped out a sync- or async-version of the same function based on the calling context (this works because everything is a…

> Zig actually also had solved the coloring problem in the old and abandondend async-await solution because the compiler simply stamped out a sync- or async-version of the same function based on the calling context (this works because everything is a single compilation unit).

AFAIK this still leaked through function pointers, which were still sync or async (and this was not visible in their type)

Re: Zig's new plan for asynchronous programs

#85

I think this design is very reasonable. However, I find Zig's explanation of it pretty confusing: they've taken pains to emphasize that it solves the function coloring problem, which it doesn't: it pushes I/O into an effect type, which essentially behaves as a token that callers need to retain. This is a form of coloring, albeit one that's much more ergonomic. (To my understanding this is pretty similar to how Go sol…

The function coloring problem actually comes up when you implement the async part using stackless coroutines (e.g. in Rust) or callbacks (e.g. in Javascript).

Zig's new I/O does neither of those for now, so hence why it doesn't suffer from it, but at the same time it didn't "solve" the problem, it just sidestepped it by providing an implementation that has similar features but not exactly the same tradeoffs.

Re: Zig's new plan for asynchronous programs

#86

I think this design is very reasonable. However, I find Zig's explanation of it pretty confusing: they've taken pains to emphasize that it solves the function coloring problem, which it doesn't: it pushes I/O into an effect type, which essentially behaves as a token that callers need to retain. This is a form of coloring, albeit one that's much more ergonomic. (To my understanding this is pretty similar to how Go sol…

Agreed. the Haskeller in me screams "You've just implemented the IO monad without language support".

It's not a monad because it doesn't return a description of how to carry out I/O that is performed by a separate system; it does the I/O inside the function before returning. That's a regular old interface, not a monad.

Re: Zig's new plan for asynchronous programs

#88
post #73

Earlier quoted context omitted.

In Zig's case you pass the argument whether or not it's asynchronous, though. The caller controls the behavior, not the function being called.

The coloring is not the concrete argument (Io implementation) that is passed, but whether the function has an Io parameter in the first place. Whether the implementation of a function performs IO is in principle an implementation detail that can change in the future. A function that doesn't take an Io argument but wants to call another function that requires an Io argument can't. So you end up adding Io parameters ju…

Is that a problem in practice though? Zig already has this same situation with its memory allocators; you can't allocate memory unless you take a parameter. Now you'll just have to take a memory allocator AND an additional io object. Doesn't sound very ergonomic to me, but if all Zig code conforms to this scheme, in practice there will only-one-way-to-do-it. So one of the colors will never be needed, or used.

Re: Zig's new plan for asynchronous programs

#89
post #82
post #80

Earlier quoted context omitted.

Let me rephrase, you can't call it like any other function. In Zig, a function that does IO can be called the same way whether or not it performs async operations or not. And if those async operations don't need concurrency (which Zig expresses separately to asynchronicity), then they'll run equally well on a sync Io runtime.

> In Zig, a function that does IO can be called the same way whether or not it performs async operations or not. no, you can't, you need to pass a IO parameter

You will need to pass that for synchronous IO as well. All IO in the standard library is moving to the Io interface. Sync and async.

If I want to call a function that does asynchronous IO, I'll use:

   foo(io, ...);
If I want to call one that does synchronous IO, I'll write:

    foo(io, ...);
If I want to express that either one of the above can be run asynchronously if possible, I'll write:

    io.async(foo, .{ io, ... });
If I want to express that it must be run concurrently, then I'll write:

    try io.concurrent(foo, .{ io, ... });
Nowhere in the above do I distinguish whether or not foo does synchronous or asynchronous IO. I only mark that it does IO, by passing in a parameter of type std.Io.

Re: Zig's new plan for asynchronous programs

#90
post #72
post #20

Earlier quoted context omitted.

A channel is not just a thread-safe queue. It's a thread-safe queue that can be used in a select call. Select is the distinguishing feature, not the queuing. I don't know enough Zig to know whether you can write a bit of code that says " either pull from this queue or that queue when they are ready"; if so, then yes they are an adequate replacement, if not, no they are not. Of course even if that exact queue is not i…

> I don't know enough Zig to know whether you can write a bit of code that says "either pull from this queue or that queue when they are ready"; if so, then yes they are an adequate replacement, if not, no they are not. Thanks for giving me a reason to peek into how Zig does things now. Zig has a generic select function[1] that works with futures. As is common, Blub's language feature is Zig's comptime function. Then…

Maybe I'm missing something, but how do you get a `Future` for receiving from a channel?

Even better, how would I write my own `Future` in a way that supports this `select` and is compatible with any reasonable `Io` implementation?

Post reply on HN