Async / Await (Current Status)
Zig once had cooperative async processing (coroutines) built into the language via the async / await / suspend / resume keywords, but these features have been temporarily disabled since Zig 0.11.0. The async implementation became too complex to maintain during the compiler's self-hosting redesign. Re-designed async support is planned for a future version, but as of the time of writing (March 2026) no timeline has been confirmed. For concurrent processing today, the practical options are OS threads via std.Thread or a community async library such as libxev.
The old async / await syntax (historical reference)
// -----------------------------------------------
// Note: The following syntax applies to Zig 0.10 and earlier.
// It produces a compile error on Zig 0.11 and later.
// Included here as a historical reference.
// -----------------------------------------------
const std = @import("std");
// Declaring an async function (0.10 and earlier)
fn fetchHp() callconv(.Async) i32 {
suspend {} // Returns control to the caller
return 1500;
}
pub fn main() void {
// async creates a frame; await waits for its result
var frame = async fetchHp();
const hp = await frame;
std.debug.print("HP: {d}\n", .{hp});
}
Summary of async / await / suspend concepts
| Keyword | Role | Status (Zig 0.11+) |
|---|---|---|
async | Creates a frame for an async function and starts execution. | Disabled. Produces a compile error. |
await | Waits for a frame to complete and retrieves its return value. | Disabled. Produces a compile error. |
suspend | Pauses the current frame and returns control to the caller. | Disabled. Produces a compile error. |
resume | Resumes a suspended frame. | Disabled. Produces a compile error. |
nosuspend | Asserts that an expression will not suspend. | Disabled. Produces a compile error. |
Current alternative: threads via std.Thread
To run multiple operations concurrently in Zig 0.11 or later, use std.Thread. It creates OS-native threads, so concurrency is preemptive rather than cooperative.
kof_thread.zig
// kof_thread.zig — Demonstrates the basics of std.Thread using KOF characters
//
// How to build:
// zig run kof_thread.zig
const std = @import("std");
// -----------------------------------------------
// Struct for arguments passed to each thread
// -----------------------------------------------
const FighterArgs = struct {
name: []const u8, // Fighter name
power: u32, // Combat power
delay_ms: u64, // Simulated delay (milliseconds)
};
// -----------------------------------------------
// Function run on each thread
// -----------------------------------------------
fn trainFighter(args: FighterArgs) void {
// Simulates processing time (equivalent to a real I/O wait)
std.time.sleep(args.delay_ms * std.time.ns_per_ms);
std.debug.print(
" [{s}] Training complete: power {d}\n",
.{ args.name, args.power },
);
}
// -----------------------------------------------
// Main function: spawns multiple threads to run concurrently
// -----------------------------------------------
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
try stdout.print("=== KOF Async Training (std.Thread) ===\n\n", .{});
// Train each fighter on a separate thread
const kyo_args = FighterArgs{ .name = "Kyo Kusanagi", .power = 9800, .delay_ms = 30 };
const iori_args = FighterArgs{ .name = "Iori Yagami", .power = 9900, .delay_ms = 10 };
const leona_args = FighterArgs{ .name = "Leona", .power = 8700, .delay_ms = 20 };
const benimaru_args = FighterArgs{ .name = "Daimon Goro", .power = 8500, .delay_ms = 25 };
// Spawn threads (execution begins immediately on spawn)
const t_kyo = try std.Thread.spawn(.{}, trainFighter, .{kyo_args});
const t_iori = try std.Thread.spawn(.{}, trainFighter, .{iori_args});
const t_leona = try std.Thread.spawn(.{}, trainFighter, .{leona_args});
const t_beni = try std.Thread.spawn(.{}, trainFighter, .{benimaru_args});
// Wait for all threads to finish (without join, threads may not complete before the process exits)
t_kyo.join();
t_iori.join();
t_leona.join();
t_beni.join();
try stdout.print("\nAll fighters have completed their training.\n", .{});
}
zig run kof_thread.zig === KOF Async Training (std.Thread) === [Iori Yagami] Training complete: power 9900 [Leona] Training complete: power 8700 [Daimon Goro] Training complete: power 8500 [Kyo Kusanagi] Training complete: power 9800 All fighters have completed their training.
Preventing data races with Mutex
When multiple threads read and write shared data simultaneously, a data race can occur. Use std.Thread.Mutex to protect the critical section.
kof_mutex.zig
// kof_mutex.zig — Updates a score counter in a thread-safe way using Mutex
//
// How to build:
// zig run kof_mutex.zig
const std = @import("std");
// -----------------------------------------------
// Shared scoreboard (written to by multiple threads simultaneously)
// -----------------------------------------------
const ScoreBoard = struct {
mutex: std.Thread.Mutex = .{}, // Mutex for locking
total: u32 = 0, // Total score
// Adds a score in a thread-safe manner
fn add(self: *ScoreBoard, name: []const u8, score: u32) void {
self.mutex.lock();
defer self.mutex.unlock(); // defer guarantees the unlock happens
self.total += score;
std.debug.print(" [{s}] +{d}pt → total: {d}pt\n", .{ name, score, self.total });
}
};
// -----------------------------------------------
// Function run on each thread
// -----------------------------------------------
const AddArgs = struct {
board: *ScoreBoard,
name: []const u8,
score: u32,
};
fn addScore(args: AddArgs) void {
// Adds the score 3 times to accumulate the total
for (0..3) |_| {
args.board.add(args.name, args.score);
std.time.sleep(5 * std.time.ns_per_ms);
}
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
try stdout.print("=== KOF Scoreboard (Mutex) ===\n\n", .{});
var board = ScoreBoard{};
const t1 = try std.Thread.spawn(.{}, addScore, .{.{ .board = &board, .name = "Kyo Kusanagi", .score = 500 }});
const t2 = try std.Thread.spawn(.{}, addScore, .{.{ .board = &board, .name = "Iori Yagami", .score = 600 }});
const t3 = try std.Thread.spawn(.{}, addScore, .{.{ .board = &board, .name = "Terry Bogard", .score = 450 }});
t1.join();
t2.join();
t3.join();
try stdout.print("\nFinal score: {d}pt\n", .{board.total});
}
zig run kof_mutex.zig === KOF Scoreboard (Mutex) === [Kyo Kusanagi] +500pt → total: 500pt [Iori Yagami] +600pt → total: 1100pt [Terry Bogard] +450pt → total: 1550pt [Kyo Kusanagi] +500pt → total: 2050pt [Terry Bogard] +450pt → total: 2500pt [Iori Yagami] +600pt → total: 3100pt [Kyo Kusanagi] +500pt → total: 3600pt [Iori Yagami] +600pt → total: 4200pt [Terry Bogard] +450pt → total: 4650pt Final score: 4650pt
External library: async I/O with libxev
| Library / Feature | Description | Use case |
|---|---|---|
| libxev | An event-loop-based async I/O library. Uses io_uring (Linux), kqueue (macOS), or IOCP (Windows) as its backend. | High-performance async processing such as network servers, timers, and file I/O. |
| Zig async redesign issue | The official tracking issue for the async redesign. Contains discussion on the current status and design direction. | Checking for the latest information. |
std.Thread | Creates OS-native threads with no additional dependencies. | CPU-bound parallel processing or simple concurrent tasks. |
std.Thread.Mutex | Protects a critical section. | Serializing access to shared data from multiple threads. |
std.Thread.Semaphore | A semaphore that limits the number of concurrent accesses. | Connection pools and capping the number of simultaneous operations. |
Summary
Zig's async / await / suspend were designed as coroutine-based cooperative async processing, but they have been temporarily disabled since Zig 0.11 as a consequence of the compiler's self-hosting redesign. The feature is expected to return in an improved form in a future version. For concurrent and parallel processing today, the recommended approach is to spawn threads with std.Thread and prevent data races with std.Thread.Mutex or std.Thread.Semaphore. For high-performance async I/O, combining a community library such as libxev is a practical option. For allocators used to manage threads and memory safely, see Allocator Basics. For combining async with error handling, see Error Handling.
If you find any errors or copyright issues, please contact us.