Comment by n42

Comment by n42 a day ago

37 replies

Aside from the ridiculous argument that function parameters color them, the assertion that you can’t call a function that takes IO from inside a function that does not is false, since you can initialize one to pass it in

dminik a day ago

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]
    • watusername 8 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...

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

    • 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 18 hours ago

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

        • masklinn 15 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 a day 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 16 hours 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.

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

  • [removed] a day ago
    [deleted]
throwawaymaths 20 hours ago

> you can’t call a function that takes IO from inside a function that does not is false, since you can initialize one to pass it in

that's not true. suppose a function foo(anytype) takes a struct, and expects method bar() on the struct.

you could send foo() the struct type Sync whose bar() does not use io. or you could send foo() the struct type Async whose bar uses an io stashed in the parameter, and there would be no code changes.

if you don't prefer compile time multireification, you can also use type erasure and accomplish the same thing with a vtable.

  • n42 14 hours ago

    It's hard to parse your comment, but I think we are agreeing? I was refuting the point of the parent. you have given another example of calling an IO-taking function inside a non-IO taking function. the example I gave was initializing an IO inside the non-IO taking function. you could also, as pointed out elsewhere, use global state.