Queue

A std.Io.Queue can be used to pass data between async functions.

The example here is an implementation of Fizz buzz using three async functions connected by queues.

const std = @import("std");
const Io = std.Io;
const Closed = Io.QueueClosedError.Closed;
const print = std.debug.print;

// gen generates numbers up to a maximum and sends
// them to a queue.
fn gen(
    io: Io,
    queue: *Io.Queue(u64),
    max: u64,
) !void {
    defer queue.close(io);
    for (1..max) |n|
        try queue.putOne(io, n);
}

// flt filters those numbers, replacing them with
// fizz/buzz/fizz buzz according to the rules of the
// game before sending them on to the next queue.
fn flt(
    io: Io,
    input: *Io.Queue(u64),
    output: *Io.Queue(Number),
) !void {
    defer output.close(io);
    while (true) {
        const n = input.getOne(io) catch |err|
            return if (err == Closed) {} else err;
        var result = Number{ .int = n };
        if (n % 5 == 0 and n % 3 == 0) {
            result = .{ .str = "fizz buzz" };
        } else if (n % 5 == 0) {
            result = .{ .str = "buzz" };
        } else if (n % 3 == 0) {
            result = .{ .str = "fizz" };
        }
        try output.putOne(io, result);
    }
}

// The elements of the second queue are either a number
// or a string, so we use a tagged union.
const Number = union(enum) {
    int: u64,
    str: []const u8,
};

// prt prints the final result.
fn prt(
    io: Io,
    queue: *Io.Queue(Number),
) !void {
    while (true) {
        const n = queue.getOne(io) catch |err|
            return if (err == Closed) {} else err;
        switch (n) {
            .int => |i| print("{d}\n", .{i}),
            .str => |s| print("{s}\n", .{s}),
        }
    }
}

// run sets up the two queues, runs the three async
// functions, and waits for them to finish.
fn run(io: Io) !void {
    var q1: Io.Queue(u64) = .init(&.{});
    var q2: Io.Queue(Number) = .init(&.{});

    // We’re using Io.concurrent here instead of
    // Io.async because Io.concurrent guarantees that
    // the functions run async. Io.async may fall back
    // to running its argument directly, but here that
    // would cause a deadlock.
    var f1 = try io.concurrent(gen, .{ io, &q1, 20 });
    defer f1.cancel(io) catch {};
    var f2 = try io.concurrent(flt, .{ io, &q1, &q2 });
    defer f2.cancel(io) catch {};
    var f3 = try io.concurrent(prt, .{ io, &q2 });
    defer f3.cancel(io) catch {};

    try f1.await(io);
    try f2.await(io);
    try f3.await(io);
}

pub fn main(init: std.process.Init) !void {
    try run(init.io);
}
$ zig run queue.zig
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizz buzz
16
17
fizz
19

Next example: File System.