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!