Live data from Hacker News

Zig's new plan for asynchronous programs

lwn.net

141–150 of 274 posts

Re: Zig's new plan for asynchronous programs

#141
post #26

I find this example quite interesting: var a_future = io.async(saveFile, .{io, data, "saveA.txt"}); var b_future = io.async(saveFile, .{io, data, "saveB.txt"}); const a_result = a_future.await(io); const b_result = b_future.await(io); In Rust or Python, if you make a coroutine (by calling an async function, for example), then that coroutine will not generally be guaranteed to make progress unless someone is waiting f…

is it not the case that in zig, the execution happens in a_future.await?

I presume that:

io.async 1 stores in io "hey please work on this"

io.async 2 stores in io "hey also please work on this"

in the case where io is evented with some "provided event loop":

await #1 runs through both 1 and 2 interleavedly, and if 2 finishes before 1, it puts a pin on it, and then returns a_result when 1 is completed.

await #2 "no-executions" if 1 finished after 2, but if there is still work to be done for 2, then it keeps going until the results for 2 are all in.

There's no "task that's running somewere mysteriously" unless you pick threaded io, in which case, yeah, io.async actually kicks shit off, and if the cpu takes a big fat nap on the calling thread between the asyncs and the awaits, progress might have been made (which wouldn't be the case if you were evented).

Re: Zig's new plan for asynchronous programs

#142
post #81

Earlier quoted context omitted.

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 __…

> 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.

In .NET the difference was known as "hot" vs "cold" tasks.

"Hot" tasks - which is what .NET does with C# async/await - have one advantage in that they get to run any code that validates the arguments right away and fail right there at the point of the call, which is easier to debug.

But one can argue that such validation should properly be separate from function body in the first place - in DbC terms it's the contract of the function.

Re: Zig's new plan for asynchronous programs

#143

Earlier quoted context omitted.

1) zig's io is not a viral effect type, you can in principle declare a global io variable and use it everywhere that any library calls for it. Not best practice for a library writer, but if you're building an app, do what you want. 2) There are two things here, there is function coloring and the function coloring problem. The function coloring problem is five things: https://journal.stuffwithstuff.com/2015/02/01/what…

> 1) zig's io is not an effect type, you can in principle declare a global io variable and use it everywhere that any library calls for it. That's an effect, akin to globally intermediated I/O in a managed runtime. To make it intuitive: if you have a global token for I/O, does your concurrent program need to synchronize on it in order to operate soundly? Do programs that fail to obtain the token behave correctly?

how do you "fail to obtain the token"?

Re: Zig's new plan for asynchronous programs

#144

Earlier quoted context omitted.

> 1) zig's io is not an effect type, you can in principle declare a global io variable and use it everywhere that any library calls for it. That's an effect, akin to globally intermediated I/O in a managed runtime. To make it intuitive: if you have a global token for I/O, does your concurrent program need to synchronize on it in order to operate soundly? Do programs that fail to obtain the token behave correctly?

how do you "fail to obtain the token"?

The token guards a fallible resource (I/O). You can (temporarily or permanently) fail to obtain it for any reason that would affect the underlying I/O.

Re: Zig's new plan for asynchronous programs

#145
post #106
post #73

Earlier quoted context omitted.

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…

> Whether the implementation of a function performs IO is in principle an implementation detail that can change in the future. I think that's where your perspective differs from Zig developers. Performing IO, in my opinion, is categorically not an implementation detail. In the same way that heap allocation is not an implementation detail in idiomatic Zig. I don't want to find out my math library is caching results on…

This is also why function coloring is not a problem, and is in fact desirable a lot of the time.

Re: Zig's new plan for asynchronous programs

#146
post #81

Earlier quoted context omitted.

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 __…

In C# that Task is ALWAYS hot, aka scheduled to run.

Re: Zig's new plan for asynchronous programs

#147
post #38

Earlier quoted context omitted.

Where do you think the Io parameter comes from? If you change some function to do something async and now suddenly you require an Io instance. I don't see the difference between having to modify the call tree to be async vs modifying the call tree to pass in an Io token.

Synchronous Io also uses the Io instance now. The coloring is no longer "is it async?" it's "does it perform Io"? This allows library authors to write their code in a manner that's agnostic to the Io runtime the user chooses, synchronous, threaded, evented with stackful coroutines, evented with stackless coroutines.

Except that now your library code lost context on how it runs. If you meant it to be sync and the caller gives you an multi threaded IO your code can fail in unexpected ways.

Re: Zig's new plan for asynchronous programs

#148

Earlier quoted context omitted.

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…

> If calling the same function with a different argument would be considered 'function coloring', than every function in a program is 'colored' and the word loses its meaning ;) I mean, the concept of "function coloring" in the first place is itself an artificial distinction invented to complain about the incongruent methods of dealing with "do I/O immediately" versus "tell me when the I/O is done"--two methods of I/…

100% agree, but fortunately I don't think it is the "default lens". If it were nobody would be adding new async mechanisms to languages, because "what color is your function" was a self-described rant against async, in favour of lightweight threads. It does seem to have established itself as an unusually persistent meme, though.

Re: Zig's new plan for asynchronous programs

#149
post #61
post #2

I like the look of this direction. I am not a fan of the `async` keyword that has become so popular in some languages that then pollutes the codebase.

Async always confused me as to when a function would actually create a new thread or not.

Why? Asynchrony has nothing to do with multiple threads. In fact you can have async with only a single thread!

Re: Zig's new plan for asynchronous programs

#150

Earlier quoted context omitted.

It's sans-io at the language level, I like the concept. So I did a bit of research into how this works in Zig under the hood, in terms of compilation. First things first, Zig does compile async fns to a state machine: https://github.com/ziglang/zig/issues/23446 The compiler decides at compile time which color to compile the function as (potentially both). That's a neat idea, but... https://github.com/ziglang/zig/issu…

> I still think sans-io at the language level might be the future, but this isn't a complete solution. Maybe we should be simply compiling all fns to state machines (with the Rust polling implementation detail, a sans-io interface could be used to make such functions trivially sync - just do the syscall and return a completed future). Can you be more specific what is missing in sans-io with explicit state machine for…

Exactly the caveat that they themselves disclose: some scenarios are too dynamic for static analysis.
Post reply on HN