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.
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.
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.
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.
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.
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...
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.
add io to a struct and let the struct keep track of its own io.
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);
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.
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.
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.
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.
it'll probably carry a stigma like using unsafe does.
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.
that's why this isn't really the same as async "coloring"