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
- Clone the repository:
git clone https://github.com/yourusername/zuki-async.git
- Add as a Git submodule to your project:
git submodule add https://github.com/yourusername/zuki-async.git deps/zuki
- 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
- Check out the Quick Start guide
- Learn about Basic Concepts
- Browse the Examples
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:
- Basic Concepts - Understand the fundamentals
- Tasks and Futures - Deep dive into task creation
- Timing and Delays - Master timeouts and delays
- 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!