Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Zuki Async Runtime

Welcome to Zuki, a high-performance async runtime for Zig that's fast, cross-platform, and zero-cost.

What is Zuki?

Zuki is an async runtime that brings structured concurrency to Zig applications. Perfect for developers looking for Zig async programming, concurrent task execution, and high-performance async patterns.

Key capabilities:

  • Task-based execution - Lightweight, composable async tasks
  • Future/Poll pattern - Similar to Rust's async model, adapted for Zig
  • Lock-free data structures - High-performance concurrent collections
  • Work stealing - Load balancing across threads
  • Zero-cost abstractions - Pay only for what you use

Key Features

High Performance

  • Lock-free queues and ring buffers
  • Work-stealing scheduler
  • Minimal allocation overhead
  • Cache-friendly data structures

Zero-Cost Abstractions

  • Compile-time optimizations
  • No hidden allocations
  • Predictable performance
  • Optional features

Cross-Platform

  • Windows, Linux, macOS support
  • Consistent behavior across platforms
  • Native threading primitives

Quick Example

const std = @import("std");
const zuki = @import("zuki");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    
    var executor = try zuki.SingleThreadedExecutor.init(gpa.allocator());
    defer executor.deinit();
    
    // Create a simple async task
    var task = MyTask{};
    _ = try executor.spawn_future(&task);
    
    // Run until completion
    try executor.run();
}

const MyTask = struct {
    pub fn poll(self: *@This(), ctx: zuki.Context) zuki.Poll(void) {
        _ = self;
        _ = ctx;
        std.debug.print("Hello from async task!\n", .{});
        return zuki.Poll(void){ .Ready = {} };
    }
};

Project Status

Early Development: Zuki is currently in active development with verbose APIs that require significant boilerplate. APIs will change frequently without notice. Not recommended for production use.

Getting Started

Ready to dive in? Head over to the Installation guide to get Zuki set up in your project.

Roadmap

Zuki Async Runtime Development Plan


This document outlines the planned features and improvements for the Zuki async runtime. The roadmap is subject to change based on community feedback and development progress.

Current Status

Warning: Early Development Zuki is currently in early development with significant limitations:

  • APIs are extremely verbose and require lots of boilerplate
  • Poor developer experience (DX) - creating simple async tasks takes too much code
  • Manual waker management and poll method implementations required
  • Single-threaded execution only
  • APIs will change frequently without notice

Do not use in production. This is currently best suited for learning async patterns and contributing to development.

Completed Features

Core Foundation

  • Basic async runtime with Future/Poll pattern
  • Single-threaded task executor with priority queues
  • Waker system for task coordination
  • Lock-free data structures (MPMC queues, ring buffers, intrusive lists)
  • Timer system with delays and timeouts
  • Basic documentation and examples

Roadmap

Short Term (Next 3-6 months)

Goal: Usable Multi-threaded Runtime

  • Multi-threaded task executor
  • Work stealing scheduler
  • Thread pool management
  • Performance optimizations

Mid Term (6-12 months)

Goal: Better Developer Experience

  • Ergonomic API redesign (reduce boilerplate significantly)
  • Simplified task creation and spawning
  • Better error handling patterns
  • Improved debugging and profiling tools

Long Term (1+ years)

Goal: Production Ready

  • Advanced scheduling (priorities, deadlines)
  • I/O integration (file, network, sockets)
  • Ecosystem packages (HTTP, databases)
  • Full benchmarking vs other runtimes
  • API stabilization (1.0 release)

Installation

Requirements

  • Zig 0.14.x - Zuki is built and tested with Zig 0.14
  • Supported Platforms: Windows, Linux, macOS (x86_64, ARM64)

Using Zig Package Manager

Add Zuki to your build.zig.zon:

.{
    .name = "my-project",
    .version = "0.1.0",
    .dependencies = .{
        .zuki = .{
            .url = "https://github.com/yourusername/zuki-async/archive/main.tar.gz",
            .hash = "1234567890abcdef...", // Use actual hash
        },
    },
}

Then in your build.zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const zuki = b.dependency("zuki", .{
        .target = target,
        .optimize = optimize,
    });

    const exe = b.addExecutable(.{
        .name = "my-app",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    
    exe.root_module.addImport("zuki", zuki.module("zuki"));
    
    b.installArtifact(exe);
}

Manual Installation

  1. Clone the repository:
git clone https://github.com/yourusername/zuki-async.git
  1. Add as a Git submodule to your project:
git submodule add https://github.com/yourusername/zuki-async.git deps/zuki
  1. In your build.zig:
const zuki = b.addModule("zuki", .{
    .root_source_file = b.path("deps/zuki/src/root.zig"),
});

exe.root_module.addImport("zuki", zuki);

Verification

Create a simple test file to verify installation:

// test.zig
const std = @import("std");
const zuki = @import("zuki");

test "zuki installation" {
    std.debug.print("Zuki version: async runtime for Zig\n", .{});
    
    // Test basic types are available
    _ = zuki.Task;
    _ = zuki.Poll;
    _ = zuki.Context;
    _ = zuki.SingleThreadedExecutor;
}

Run with:

zig test test.zig

If you see the version message and no errors, you're ready to go!

Next Steps

Quick Start

This guide will get you running your first async task with Zuki in minutes.

Your First Async Task

Let's create a simple program that runs multiple tasks concurrently:

const std = @import("std");
const zuki = @import("zuki");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    
    var executor = try zuki.SingleThreadedExecutor.init(gpa.allocator());
    defer executor.deinit();
    
    std.debug.print("Starting async tasks...\n", .{});
    
    // Create multiple tasks
    var task1 = PrintTask{ .id = 1, .message = "Hello" };
    var task2 = PrintTask{ .id = 2, .message = "World" };
    var task3 = CountTask{ .target = 5 };
    
    // Spawn them
    _ = try executor.spawn_future(&task1);
    _ = try executor.spawn_future(&task2);
    _ = try executor.spawn_future(&task3);
    
    // Run until all tasks complete
    try executor.run();
    
    std.debug.print("All tasks completed!\n", .{});
}

const PrintTask = struct {
    id: u32,
    message: []const u8,
    
    pub fn poll(self: *@This(), ctx: zuki.Context) zuki.Poll(void) {
        _ = ctx;
        std.debug.print("Task {}: {s}\n", .{ self.id, self.message });
        return zuki.Poll(void){ .Ready = {} };
    }
};

const CountTask = struct {
    target: u32,
    current: u32 = 0,
    
    pub fn poll(self: *@This(), ctx: zuki.Context) zuki.Poll(void) {
        if (self.current >= self.target) {
            std.debug.print("Counting complete: {}\n", .{self.current});
            return zuki.Poll(void){ .Ready = {} };
        }
        
        self.current += 1;
        std.debug.print("Count: {}\n", .{self.current});
        
        // Wake ourselves up for the next iteration
        ctx.waker.wake();
        return zuki.Poll(void){ .Pending = {} };
    }
};

Save this as example.zig and run:

zig run example.zig

You should see output like:

Starting async tasks...
Task 1: Hello
Task 2: World
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Counting complete: 5
All tasks completed!

Adding Delays

Let's make things more interesting with timing:

const std = @import("std");
const zuki = @import("zuki");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    
    var executor = try zuki.SingleThreadedExecutor.init(gpa.allocator());
    defer executor.deinit();
    
    var timer = zuki.Timer.init(gpa.allocator());
    defer timer.deinit();
    
    // Create a task that waits 1 second
    var delay_task = DelayTask{ .timer = &timer };
    _ = try executor.spawn_future(&delay_task);
    
    // Run the executor, processing timers
    while (true) {
        // Process any expired timers
        try timer.process_expired();
        
        // Run one step of the executor
        const has_more = try executor.step();
        if (!has_more) break;
        
        // Short sleep to avoid busy loop
        std.time.sleep(1000000); // 1ms
    }
    
    std.debug.print("All done!\n", .{});
}

const DelayTask = struct {
    timer: *zuki.Timer,
    delay_future: ?zuki.DelayFuture = null,
    
    pub fn poll(self: *@This(), ctx: zuki.Context) zuki.Poll(void) {
        if (self.delay_future == null) {
            std.debug.print("Starting delay...\n", .{});
            self.delay_future = zuki.DelayFuture.from_secs(self.timer, 1);
        }
        
        const result = self.delay_future.?.poll(ctx);
        switch (result) {
            .Ready => {
                std.debug.print("Delay completed!\n", .{});
                return zuki.Poll(void){ .Ready = {} };
            },
            .Pending => return zuki.Poll(void){ .Pending = {} },
        }
    }
};

What's Next?

Great! You've run your first Zuki async tasks. Here's what to explore next:

  1. Basic Concepts - Understand the fundamentals
  2. Tasks and Futures - Deep dive into task creation
  3. Timing and Delays - Master timeouts and delays
  4. Examples - More real-world examples

Common Patterns

Error Handling

const MyTask = struct {
    pub fn poll(self: *@This(), ctx: zuki.Context) zuki.Poll(anyerror!void) {
        _ = self;
        _ = ctx;
        
        // Simulate work that might fail
        if (std.crypto.random.boolean()) {
            return zuki.Poll(anyerror!void){ .Ready = error.SomethingWentWrong };
        }
        
        return zuki.Poll(anyerror!void){ .Ready = {} };
    }
};

Stateful Tasks

const StatefulTask = struct {
    state: enum { Init, Working, Done } = .Init,
    work_done: u32 = 0,
    
    pub fn poll(self: *@This(), ctx: zuki.Context) zuki.Poll(void) {
        switch (self.state) {
            .Init => {
                std.debug.print("Initializing...\n", .{});
                self.state = .Working;
                ctx.waker.wake();
                return zuki.Poll(void){ .Pending = {} };
            },
            .Working => {
                self.work_done += 1;
                if (self.work_done >= 10) {
                    self.state = .Done;
                    ctx.waker.wake();
                    return zuki.Poll(void){ .Pending = {} };
                }
                ctx.waker.wake();
                return zuki.Poll(void){ .Pending = {} };
            },
            .Done => {
                std.debug.print("Work completed: {}\n", .{self.work_done});
                return zuki.Poll(void){ .Ready = {} };
            },
        }
    }
};

Now you're ready to build more complex async applications with Zuki!

Basic Concepts

Tasks and Futures

Executors

Timing and Delays

Error Handling

Concurrent Data Structures

Work Stealing

Performance Tuning

Custom Futures

Core Types

Runtime

Time

Concurrent

Simple Tasks

Timeouts and Delays

Concurrent Processing

Contributing

Architecture

Testing

Changelog

FAQ