Comment by dminik

Comment by dminik a day ago

6 replies

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

mlugg a day ago

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

If `foo` needs to do IO, sure. Or, more typically (as I mentioned in a different comment), it's something like `const x = something.foo()`, and `foo` can get its `Io` instance from `something` (in the Zig compiler this would be a `Compilation` or a `Zcu` or a `Sema` or something like that).

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

Sure, but that's abstracted away from you. To be clear, stackless coroutines are the only case where the codegen of callers is affected, which is why they require a language feature. Even if your application uses two `Io` implementations for some reason, one of which is based on stackless coroutines, functions using the API are not duplicated.

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

Mixing futures from any two different `Io` implementations will typically result in Illegal Behavior -- just like passing a pointer allocated with one `Allocator` into the `free` of a different `Allocator` does. This really isn't a problem. Even with allocators, it's pretty rare for people to mess this up, and with allocators you often do have multiple of them available in one place (e.g. a gpa and an arena). In contrast, it will be extraordinarily rare to have more than one `Io` lying around. Even if you do mess it up, the IB will probably just trip a safety check, so it shouldn't take you too long to realise what you've done.

  • dminik a day ago

    I find these two statements to be contradictory

    > Sure, but that's abstracted away from you

    > Mixing futures from any two different `Io` implementations will typically result in Illegal Behavior

    Thinking about it more, you've possibly added even more colors. Each executor adds a different color and while each function is color-agnostic (but not colorless) futures aren't.

    > it will be extraordinarily rare to have more than one `Io`

    Will it? I can immediately think of a use case where a program might want to block for files on disk, but defer fetching from network to some background async executor.

  • dminik a day ago

    Also, I do find it funny that we went from "Zig has completely defeated function coloring" to "Zig has colored objects".

    • throwawaymaths a day ago

      but that's not even the case, because it's certainly possible to write a function that receives an object that holds onto an io (and uses it in its vtable calls) that equally well receives an object that doesn't have anything to do with io [0]. The consumers of those objects don't have to care, so there's no coloring.

      [0] and this isn't even really a theoretical matter, having colorblind object passing is extremely useful for say, mocking. Oh, I have a database lookup/remote API call, which obviously requires io, but i want fast tests and I can mock it with an object with preseeded values/expects -- hey, that doesn't require IO.

      • dminik 13 hours ago

        I think in practice the caller still needs to know.

        If I call `a.foo()` but `a` has and is using a stackless coroutine IO but the caller is being executed from a green thread IO then as was said before, I'm hitting UB.

        But, I do like that you could skip/mock IO for instance. That's pretty neat.

        • throwawaymaths 8 hours ago

          here is example code. you wont "use the wrong io".

              const VTable = struct {
                f: &fn (*VTable) void,
              };
          
              const A = struct {
                io: IO,
                v: VTable = .{ .f = &A.uses_io },
                fn uses_io(this: *VTable) void {
                  const self: *A = @fieldParentPtr(.v, this);
                  self.io.some_io_fn(...);
                }
              };
          
              const B = struct{v: VTable = .{.f = &void_fn}};
              fn void_fn(_: *VTable) void {}
          
              pub fn calls_vtable(v: VTable) {
                v.f()
              }