Comment by dminik

Comment by dminik 2 days ago

35 replies

To me, there's no difference between the IO param and async/await. Adding either one causes it to not be callable from certain places.

As for the second thing:

You can do that, but... You can also do this in Rust. Yet nobody would say Rust has solved function coloring.

Also, check this part of the article:

> In the less common case when a program instantiates more than one Io implementation, virtual calls done through the Io interface will not be de-virtualized, ...

Doing that is an instant performance hit. Not to mention annoying to do.

delamon a day ago

> Doing that is an instant performance hit. Not to mention annoying to do.

The cost of virtual dispatch on IO path is almost always negligible. It is literally one conditional vs syscall. I doubt it you can even measure the difference.

bmurphy1976 a day ago

>To me, there's no difference between the IO param and async/await.

You can't pass around "async/await" as a value attached to another object. You can do that with the IO param. That is very different.

  • jolux a day ago

    > You can't pass around "async/await" as a value attached to another object

    Sure you can? You can just pass e.g. a Task around in C# without awaiting it, it's when you need a result from a task that you must await it.

    • [removed] a day ago
      [deleted]
  • MrJohz a day ago

    Sure you can. An `async` function in Javascript is essentially a completely normal function that returns a promise. The `async`/`await` syntax is a convenient syntax sugar for working with promises, but the issue would still exist if it didn't exist.

    More to the point, the issue would still exist even if promises didn't exist — a lot of Node APIs originally used callbacks and a continuation-passing style approach to concurrency, and that had exactly the same issues.

  • watusername 14 hours ago

    Other commenters have already provided examples for other languages, and it's the same for Rust: async functions are just regular functions that return an impl Future type. As a sync function, you can call a bunch of async functions and return the futures to your caller to handle, or you can block your current thread with the block_on function typically available through a handle (similar to the Io object here) provided by your favorite async runtime [0].

    In other words, you don't need such an Io object upfront: You need it when you want to actually drive its execution and get the result. From this perspective, the Zig approach is actually less flexible than Rust.

    [0]: https://docs.rs/tokio/latest/tokio/runtime/struct.Handle.htm...

  • dminik a day ago

    Conceptually, there's not much of a difference.

    If you have a sync/non-IO function that now needs to do IO, it becomes async/IO. And since IO and async are viral, it's callers must also now be IO/async and call it with IO/await. All the way up the call stack.

throwawaymaths a day ago

> Adding either one causes it to not be callable from certain places.

you can call a function that requires an io parameter from a function that doesn't have one by passing in a global io instance?

as a trivial example the fn main entrypoint in zig will never take an io parameter... how do you suppose you'd bootstrap the io parameter that you'd eventually need. this is unlike other languages where main might or might not be async.

  • masklinn a day ago

    You can call an async function from a function that is not async by passing in a global runtime (/ event loop).

    As a trivial example the main entry point in rust is never async. How’d you suppose you’d bootstrap the runtime that you’d eventually need.

    This is pretty much like every other langage.

    • throwawaymaths a day ago

      and yet... there are libraries that were written twice, once for async and once for not.

      • masklinn 21 hours ago

        Which should be a pretty big hint that you've misidentified the issue.

    • almostgotcaught a day ago

      People in software really do have poor abilities to see the forest for the trees don't they lol

  • ginko a day ago

    >you can call a function that requires an io parameter from a function that doesn't have one by passing in a global io instance?

    How will that work with code mixing different Io implementations? Say a library pulled in uses a global Io instance while the calling code is using another.

    I guess this can just be shot down with "don't do that" but it feels like a new kind of pitfall get get into.

    • throwawaymaths a day ago

      > Say a library pulled in uses a global Io instance while the calling code is using another.

      it'll probably carry a stigma like using unsafe does.

    • TUSF a day ago

      Zig already has an Allocator interface that gets passed around, and the convention is that libraries don't select an Allocator. Only provide APIs that accept allocators. If there's a certain process that works best with an Arena, then the API may wrap a provided function in an Arena, but not decide on their own underlying allocator for the user.

      For Zig users, adopting this same mindset for Io is not really anything new. It's just another parameter that occasionally needs to be passed into an API.

    • kristoff_it a day ago

      while not really idiomatic, as long as you let the user define the Io instance (eg with some kind of init function), then it doesn't really matter how that value is accessed within the library itself.

      that's why this isn't really the same as async "coloring"

n42 2 days ago

You’re allowed to not like it, but that doesn’t change that your argument that this is a form of coloring is objectively false. I’m not sure what Rust has to do with it.

  • dminik a day ago

    It's funny, but I do actually like it. It's just that it walks like a duck, swims like a duck and quacks like a duck.

    I don't have a problem with IO conceptually (but I do have a problem with Zig ergonomics, allocator included). I do have a problem with claiming you defeated function coloring.

    Like, look. You didn't even get rid of await ...

    > try a_future.await(io);

    • n42 a day ago

      I do want to say that I regretted that comment as nonconstructive after it was too late to edit it. Others in the thread are representing my argument better than I can or care to.

    • mlugg a day ago

      I mean... you use `await` if you've used `async`. It's your choice whether or not you do; and if you don't want to, your callers and callees can still freely `async` and `await` if they want to. I don't understand the point you're trying to make here.

      To be clear, where many languages require you to write `const x = await foo()` every time you want to call an async function, in Zig that's just `const x = foo()`. This is a key part of the colorless design; you can't be required to acknowledge that a function is async in order to use it. You'll only use `await` if you first use `async` to explicitly say "I want to run this asynchronously with other code here if possible". If you need the result immediately, that's just a function call. Either way, your caller can make its own choice to call you or other functions as `async`, or not to; as can your callees.

      • dminik a day ago

        > in Zig that's just ...

        Well, no. In zig that's `const x = foo(io)`.

        The moment you take or even know about an io, your function is automatically "generic" over the IO interface.

        Using stackless coroutines and green threads results in a completely different codegen.

        I just noticed this part of the article:

        > Stackless Coroutines > > This implementation won’t be available immediately like the previous ones because it depends on reintroducing a special function calling convention and rewriting function bodies into state machines that don’t require an explicit stack to run. > > This execution model is compatible with WASM and other platforms where stack swapping is not available or desireable.

        I wonder what will happen if you try to await a future created with a green thread IO using a stackless coroutine IO.

  • rowanG077 a day ago

    Sure it is a function coloring. Just in a different form. `async` in other languages is something like an implicit parameter. In zig they made this implicit parameter explicit. Is that more better/more ergonomic? I don't know yet. The sugar is different, but the end result the same. Unless you can show me concrete example of things that the approach zig has taken can do that is not possible in say, rust. Than I don't buy that it's not just another form of function coloring.

    • throwawaymaths a day ago

      > Unless you can show me concrete example

      add io to a struct and let the struct keep track of its own io.

      • rowanG077 a day ago

        You can also carry around a runtime and dispatch async function in non-async functions.

      • gfaster a day ago

        Unless I'm misunderstanding, that's effectively implementing Future for the struct