//onReady takes a callback and a frame pointer, //and then at some point passes the frame pointer to the callback. //the frame pointer is typed as anyopaque (same as c void pointer) //instead of any frame because of compiler errors in get extern fn onReady( cb: fn ( frame: *anyopaque ) callconv(.C) void, frame: *anyopaque ) void; //copies memory from host into pointer, up to len extern fn read(ptr: [*]const u8, len:usize) usize; //writes len bytes from ptr to stdout extern fn print(ptr: [*]const u8, len:usize) void; fn get (slice: []u8) usize { //put a suspend here, otherwise async get() //will run to the end then return frame at return value. //we don't want to do that because memory to read isn't ready yet. suspend { //because we pass a pointer to our own frame to an external function //that parameter must be typed *anyopaque. if it's typed anyframe //there are compiler errors that "@Frame(get) not analyzed yet" onReady(cb, @frame()); } return read(slice.ptr, slice.len); } fn cb (frame: *anyopaque) callconv(.C) void { //defer allocator.destroy(frame); //cast frame:*anyopaque to frame:*anyframe so we can resume it. //this requires also casting the alignment. resume @ptrCast(anyframe, @alignCast(4, frame)); } //internal _init function, this can have any sort of async pattern. fn _init () void { var array1:[1]u8 = [_]u8{0}; //note: because of zig's uncoloured async, this just looks like a normal loop while (0 != get(array1[0..array1.len])) { print(&array1, 1); } } //this is the essential trick, store the _init frame in global variable. var init_frame :@Frame(_init) = undefined; //the exported function can't be async //but we can call another function with async keyword //but if we did that with a local variable the memory wouldn't be alive //after init() returns //so, we make it a global. then, call async _init //and have any kind of async pattern in there. //(note, this does mean that init() should only be called exactly once, like main()) export fn init () void { init_frame = async _init(); }