summaryrefslogtreecommitdiff
path: root/exercises
diff options
context:
space:
mode:
authorChris Boesch <chrboesch@noreply.codeberg.org>2026-04-07 09:18:37 +0200
committerChris Boesch <chrboesch@noreply.codeberg.org>2026-04-07 09:18:37 +0200
commit7cb7a9948a9f5c9965fedd59f0cd816f28962fe4 (patch)
tree1953e653faba351ab103ec030eacbfc79b7dae6c /exercises
parent1166f3cfb65d7edc9086c6ba6b8edd4754964b33 (diff)
parent3574fd3ae037f34510c6bb6657332b57447ac5b1 (diff)
Merge branch 'main' into fix-060
Diffstat (limited to 'exercises')
-rw-r--r--exercises/084_async.zig58
-rw-r--r--exercises/084_interfaces.zig (renamed from exercises/092_interfaces.zig)2
-rw-r--r--exercises/085_async.zig49
-rw-r--r--exercises/085_async2.zig28
-rw-r--r--exercises/086_async2.zig54
-rw-r--r--exercises/086_async3.zig29
-rw-r--r--exercises/087_async3.zig49
-rw-r--r--exercises/087_async4.zig30
-rw-r--r--exercises/088_async4.zig50
-rw-r--r--exercises/088_async5.zig48
-rw-r--r--exercises/089_async5.zig67
-rw-r--r--exercises/089_async6.zig54
-rw-r--r--exercises/090_async6.zig76
-rw-r--r--exercises/090_async7.zig87
-rw-r--r--exercises/091_async7.zig57
-rw-r--r--exercises/091_async8.zig35
-rw-r--r--exercises/092_async8.zig62
-rw-r--r--exercises/093_async9.zig69
-rw-r--r--exercises/094_async10.zig68
-rw-r--r--exercises/095_quiz_async.zig188
-rw-r--r--exercises/096_hello_c.zig (renamed from exercises/093_hello_c.zig)0
-rw-r--r--exercises/097_c_math.zig (renamed from exercises/094_c_math.zig)0
-rw-r--r--exercises/098_for3.zig (renamed from exercises/095_for3.zig)0
-rw-r--r--exercises/099_memory_allocation.zig (renamed from exercises/096_memory_allocation.zig)0
-rw-r--r--exercises/100_bit_manipulation.zig (renamed from exercises/097_bit_manipulation.zig)0
-rw-r--r--exercises/101_bit_manipulation2.zig (renamed from exercises/098_bit_manipulation2.zig)0
-rw-r--r--exercises/102_formatting.zig (renamed from exercises/099_formatting.zig)0
-rw-r--r--exercises/103_for4.zig (renamed from exercises/100_for4.zig)0
-rw-r--r--exercises/104_for5.zig (renamed from exercises/101_for5.zig)0
-rw-r--r--exercises/105_testing.zig (renamed from exercises/102_testing.zig)0
-rw-r--r--exercises/106_tokenization.zig (renamed from exercises/103_tokenization.zig)0
-rw-r--r--exercises/107_threading.zig (renamed from exercises/104_threading.zig)53
-rw-r--r--exercises/108_threading2.zig (renamed from exercises/105_threading2.zig)0
-rw-r--r--exercises/109_files.zig (renamed from exercises/106_files.zig)0
-rw-r--r--exercises/110_files2.zig (renamed from exercises/107_files2.zig)0
-rw-r--r--exercises/111_labeled_switch.zig (renamed from exercises/108_labeled_switch.zig)0
-rw-r--r--exercises/112_vectors.zig (renamed from exercises/109_vectors.zig)0
-rw-r--r--exercises/113_quiz9.zig (renamed from exercises/110_quiz9.zig)0
-rw-r--r--exercises/114_packed.zig (renamed from exercises/111_packed.zig)0
-rw-r--r--exercises/115_packed2.zig (renamed from exercises/112_packed2.zig)0
40 files changed, 812 insertions, 401 deletions
diff --git a/exercises/084_async.zig b/exercises/084_async.zig
deleted file mode 100644
index 56c9969..0000000
--- a/exercises/084_async.zig
+++ /dev/null
@@ -1,58 +0,0 @@
-//
-// Six Facts:
-//
-// 1. The memory space allocated to your program for the
-// invocation of a function and all of its data is called a
-// "stack frame".
-//
-// 2. The 'return' keyword "pops" the current function
-// invocation's frame off of the stack (it is no longer needed)
-// and returns control to the place where the function was
-// called.
-//
-// fn foo() void {
-// return; // Pop the frame and return control
-// }
-//
-// 3. Like 'return', the 'suspend' keyword returns control to the
-// place where the function was called BUT the function
-// invocation's frame remains so that it can regain control again
-// at a later time. Functions which do this are "async"
-// functions.
-//
-// fn fooThatSuspends() void {
-// suspend {} // return control, but leave the frame alone
-// }
-//
-// 4. To call any function in async context and get a reference
-// to its frame for later use, use the 'async' keyword:
-//
-// var foo_frame = async fooThatSuspends();
-//
-// 5. If you call an async function without the 'async' keyword,
-// the function FROM WHICH you called the async function itself
-// becomes async! In this example, the bar() function is now
-// async because it calls fooThatSuspends(), which is async.
-//
-// fn bar() void {
-// fooThatSuspends();
-// }
-//
-// 6. The main() function cannot be async!
-//
-// Given facts 3 and 4, how do we fix this program (broken by facts
-// 5 and 6)?
-//
-const print = @import("std").debug.print;
-
-pub fn main() void {
- // Additional Hint: you can assign things to '_' when you
- // don't intend to do anything with them.
- foo();
-}
-
-fn foo() void {
- print("foo() A\n", .{});
- suspend {}
- print("foo() B\n", .{});
-}
diff --git a/exercises/092_interfaces.zig b/exercises/084_interfaces.zig
index 7775dd5..fd61ead 100644
--- a/exercises/092_interfaces.zig
+++ b/exercises/084_interfaces.zig
@@ -102,7 +102,7 @@ pub fn main() !void {
Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } },
};
- std.debug.print("Daily Insect Report:\n", .{});
+ std.debug.print("=== Doctor Zoraptera's Insect Report ===\n", .{});
for (my_insects) |insect| {
// Almost done! We want to print() each insect with a
// single method call here.
diff --git a/exercises/085_async.zig b/exercises/085_async.zig
new file mode 100644
index 0000000..1d885a5
--- /dev/null
+++ b/exercises/085_async.zig
@@ -0,0 +1,49 @@
+//
+// In previous versions of Zig, async/await used special keywords
+// like 'suspend', 'resume', and 'async' that operated on stackframes
+// directly. Those keywords no longer exist!
+//
+// Zig 0.16 replaced them with a unified I/O interface: std.Io.
+// This interface uses a VTable pattern - a struct of function pointers -
+// to abstract over different concurrency backends:
+//
+// * Threaded - thread-pool based I/O
+// * Evented - chooses the best event-loop backend for your OS:
+// * Uring on Linux (io_uring)
+// * Kqueue on BSD/macOS
+// * Dispatch on macOS (Grand Central Dispatch)
+//
+// The Io struct itself is tiny:
+//
+// const Io = struct {
+// userdata: ?*anyopaque, // opaque state of the backend
+// vtable: *const VTable, // table of function pointers
+// };
+//
+// Your code receives an Io value and calls methods on it.
+// The backend is chosen at initialization time - your code doesn't
+// need to know which one it is!
+//
+// In Zig 0.16, main() receives a std.process.Init struct to opt
+// into I/O and concurrency support:
+//
+// pub fn main(init: std.process.Init) !void {
+// const io = init.io;
+// // ... use io ...
+// }
+//
+// Let's start simple. Fix the main function to extract the Io
+// interface from init, then use it to get the current time.
+//
+const std = @import("std");
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.???;
+
+ // Get the current wall-clock time using the Io interface.
+ // Hint: Timestamp.now() takes an Io and a Clock type (.real = wall clock).
+ const timestamp = std.Io.Timestamp.now(io, .real);
+
+ // Print the timestamp in seconds since the Unix epoch.
+ std.debug.print("Current time: {}s since epoch\n", .{timestamp.toSeconds()});
+}
diff --git a/exercises/085_async2.zig b/exercises/085_async2.zig
deleted file mode 100644
index 036aefa..0000000
--- a/exercises/085_async2.zig
+++ /dev/null
@@ -1,28 +0,0 @@
-//
-// So, 'suspend' returns control to the place from which it was
-// called (the "call site"). How do we give control back to the
-// suspended function?
-//
-// For that, we have a new keyword called 'resume' which takes an
-// async function invocation's frame and returns control to it.
-//
-// fn fooThatSuspends() void {
-// suspend {}
-// }
-//
-// var foo_frame = async fooThatSuspends();
-// resume foo_frame;
-//
-// See if you can make this program print "Hello async!".
-//
-const print = @import("std").debug.print;
-
-pub fn main() void {
- var foo_frame = async foo();
-}
-
-fn foo() void {
- print("Hello ", .{});
- suspend {}
- print("async!\n", .{});
-}
diff --git a/exercises/086_async2.zig b/exercises/086_async2.zig
new file mode 100644
index 0000000..cf376e2
--- /dev/null
+++ b/exercises/086_async2.zig
@@ -0,0 +1,54 @@
+//
+// Now that we know how to get an Io value, let's use it for
+// asynchronous execution!
+//
+// io.async() launches a function and returns a Future. The result
+// won't necessarily be available until you call .await() on it:
+//
+// var future = io.async(someFunction, .{ arg1, arg2 });
+// const result = future.await(io);
+//
+// The function *may* run immediately or on another thread -
+// your code doesn't need to care! That's the beauty of the
+// Io abstraction.
+//
+// IMPORTANT: Every Future MUST be either .await()ed or .cancel()ed.
+// Failing to do so leaks resources! A safe pattern is:
+//
+// var future = io.async(myFn, .{});
+// defer _ = future.cancel(io); // safety net
+// // ... later, if we want the result:
+// const result = future.await(io);
+// // (await after cancel is fine — it just returns the result)
+//
+// Both .await() and .cancel() block until the task finishes and
+// return the result. The difference is that .cancel() also
+// requests the task to stop at its next cancellation point.
+// Calling either one more than once is safe — subsequent calls
+// just return a copy of the result.
+//
+// Fix this program so that computeAnswer runs asynchronously
+// and its result is properly awaited.
+//
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+
+ // Launch computeAnswer asynchronously.
+ var future = io.async(computeAnswer, .{ 6, 7 });
+ defer _ = future.cancel(io); // always clean up!
+
+ print("Computing... ", .{});
+
+ // Now collect the result. What method on Future gives us
+ // the value, blocking until it's ready?
+ const answer = future.???(io);
+
+ print("The answer is: {}\n", .{answer});
+}
+
+fn computeAnswer(a: u32, b: u32) u32 {
+ return a * b;
+}
diff --git a/exercises/086_async3.zig b/exercises/086_async3.zig
deleted file mode 100644
index c8f1113..0000000
--- a/exercises/086_async3.zig
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-// Because they can suspend and resume, async Zig functions are
-// an example of a more general programming concept called
-// "coroutines". One of the neat things about Zig async functions
-// is that they retain their state as they are suspended and
-// resumed.
-//
-// See if you can make this program print "5 4 3 2 1".
-//
-const print = @import("std").debug.print;
-
-pub fn main() void {
- const n = 5;
- var foo_frame = async foo(n);
-
- ???
-
- print("\n", .{});
-}
-
-fn foo(countdown: u32) void {
- var current = countdown;
-
- while (current > 0) {
- print("{} ", .{current});
- current -= 1;
- suspend {}
- }
-}
diff --git a/exercises/087_async3.zig b/exercises/087_async3.zig
new file mode 100644
index 0000000..d10052c
--- /dev/null
+++ b/exercises/087_async3.zig
@@ -0,0 +1,49 @@
+//
+// The real power of async shows when you launch MULTIPLE tasks!
+//
+// With io.async(), you can start several operations, then await
+// them all. The Io backend may run them concurrently:
+//
+// var f1 = io.async(taskA, .{});
+// defer _ = f1.cancel(io);
+// var f2 = io.async(taskB, .{});
+// defer _ = f2.cancel(io);
+// const a = f1.await(io);
+// const b = f2.await(io);
+//
+// Notice the defer pattern: each async call is immediately
+// followed by a defer cancel. This ensures cleanup even if
+// we return early or hit an error before reaching await.
+// Since await/cancel are idempotent, the defer is harmless
+// if we've already awaited.
+//
+// Fix this program to launch both tasks and collect their results.
+//
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+
+ // Launch both tasks asynchronously.
+ var future_a = io.async(slowAdd, .{ 1, 2 });
+ defer _ = future_a.cancel(io);
+ var future_b = ???(slowMul, .{ 6, 7 });
+ defer _ = future_b.cancel(io);
+
+ // Await both results.
+ const sum = future_a.await(io);
+ const product = future_b.await(io);
+
+ print("{} + {} = {}\n", .{ 1, 2, sum });
+ print("{} * {} = {}\n", .{ 6, 7, product });
+ print("Total: {}\n", .{sum + product});
+}
+
+fn slowAdd(a: u32, b: u32) u32 {
+ return a + b;
+}
+
+fn slowMul(a: u32, b: u32) u32 {
+ return a * b;
+}
diff --git a/exercises/087_async4.zig b/exercises/087_async4.zig
deleted file mode 100644
index bb9c9ec..0000000
--- a/exercises/087_async4.zig
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// It has probably not escaped your attention that we are no
-// longer capturing a return value from foo() because the 'async'
-// keyword returns the frame instead.
-//
-// One way to solve this is to use a global variable.
-//
-// See if you can make this program print "1 2 3 4 5".
-//
-const print = @import("std").debug.print;
-
-var global_counter: i32 = 0;
-
-pub fn main() void {
- var foo_frame = async foo();
-
- while (global_counter <= 5) {
- print("{} ", .{global_counter});
- ???
- }
-
- print("\n", .{});
-}
-
-fn foo() void {
- while (true) {
- ???
- ???
- }
-}
diff --git a/exercises/088_async4.zig b/exercises/088_async4.zig
new file mode 100644
index 0000000..8298ca1
--- /dev/null
+++ b/exercises/088_async4.zig
@@ -0,0 +1,50 @@
+//
+// When you have many tasks that don't return individual values,
+// use a Group! A Group is an unordered set of tasks that can
+// only be awaited or canceled as a whole:
+//
+// var group: std.Io.Group = .init;
+// group.async(io, myTask, .{arg1});
+// group.async(io, myTask, .{arg2});
+// try group.await(io); // blocks until ALL tasks finish
+//
+// Important rules:
+// * The return type of functions spawned in a group must be
+// coercible to Cancelable!void (i.e. void, or error{Canceled}!void).
+// * Once you call group.async(), you MUST eventually call
+// group.await() or group.cancel() to release resources.
+// * group.cancel() requests cancellation on ALL members,
+// then blocks until they all finish.
+//
+// Unlike Future, Group tasks don't return values to the caller.
+// They're ideal for parallel work that communicates through
+// shared state or side effects (like printing).
+//
+// Fix this program to await all tasks in the group.
+//
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+
+ var group: std.Io.Group = .init;
+
+ // Spawn 3 tasks in any order. Each sleeps for (id * 1) seconds
+ // before printing, so the output order is deterministic.
+ group.async(io, doWork, .{ io, 1 });
+ group.async(io, doWork, .{ io, 3 });
+ group.async(io, doWork, .{ io, 2 });
+
+ // Wait for all tasks to finish.
+ // What Group method blocks until all tasks complete?
+ try group.???(io);
+
+ print("All tasks finished!\n", .{});
+}
+
+fn doWork(io: std.Io, id: u32) void {
+ // Sleep ensures deterministic output order.
+ io.sleep(std.Io.Duration.fromSeconds(id), .awake) catch return;
+ print("Task {} done.\n", .{id});
+}
diff --git a/exercises/088_async5.zig b/exercises/088_async5.zig
deleted file mode 100644
index 759a920..0000000
--- a/exercises/088_async5.zig
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-// Sure, we can solve our async value problem with a global
-// variable. But this hardly seems like an ideal solution.
-//
-// So how do we REALLY get return values from async functions?
-//
-// The 'await' keyword waits for an async function to complete
-// and then captures its return value.
-//
-// fn foo() u32 {
-// return 5;
-// }
-//
-// var foo_frame = async foo(); // invoke and get frame
-// var value = await foo_frame; // await result using frame
-//
-// The above example is just a silly way to call foo() and get 5
-// back. But if foo() did something more interesting such as wait
-// for a network response to get that 5, our code would pause
-// until the value was ready.
-//
-// As you can see, async/await basically splits a function call
-// into two parts:
-//
-// 1. Invoke the function ('async')
-// 2. Getting the return value ('await')
-//
-// Also notice that a 'suspend' keyword does NOT need to exist in
-// a function to be called in an async context.
-//
-// Please use 'await' to get the string returned by
-// getPageTitle().
-//
-const print = @import("std").debug.print;
-
-pub fn main() void {
- var myframe = async getPageTitle("http://example.com");
-
- var value = ???
-
- print("{s}\n", .{value});
-}
-
-fn getPageTitle(url: []const u8) []const u8 {
- // Please PRETEND this is actually making a network request.
- _ = url;
- return "Example Title.";
-}
diff --git a/exercises/089_async5.zig b/exercises/089_async5.zig
new file mode 100644
index 0000000..0c00c1f
--- /dev/null
+++ b/exercises/089_async5.zig
@@ -0,0 +1,67 @@
+//
+// One of the most important features of the new Io system is
+// structured cancellation!
+//
+// Every Future has a .cancel() method that:
+// 1. Requests the task to stop (via error.Canceled at the
+// next "cancellation point")
+// 2. BLOCKS until the task actually finishes
+// 3. Returns whatever result the task produced
+//
+// A "cancellation point" is any Io function that can return
+// error.Canceled - most commonly io.sleep():
+//
+// fn myTask(io: std.Io) u32 {
+// io.sleep(...) catch |err| switch (err) {
+// error.Canceled => return 0, // error handle
+// };
+// return 42;
+// }
+//
+// This is fundamentally different from killing a thread -
+// the task gets a chance to clean up and return a value!
+//
+// Remember: both .await() and .cancel() block and return the
+// result. The only difference is that .cancel() also sends
+// the cancellation request. And both are idempotent — calling
+// either one again just returns the same result.
+//
+// Fix this program: the slow task would take 10 seconds,
+// but we cancel it after 1 second. The task should detect
+// the cancellation and return early.
+//
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+
+ var future = io.async(slowTask, .{io});
+ defer _ = future.cancel(io); // safety net
+
+ // Wait 1 second, then cancel instead of waiting the full 10.
+ io.sleep(std.Io.Duration.fromSeconds(1), .awake) catch {};
+
+ print("Canceling slow task...\n", .{});
+
+ // We don't want to wait 10 seconds!
+ // Which Future method requests cancellation AND returns the result?
+ const result = future.???(io);
+
+ print("Task returned: {}\n", .{result});
+}
+
+fn slowTask(io: std.Io) u32 {
+ print("Starting long computation...\n", .{});
+
+ // Try to sleep for 10 seconds - but we might get canceled!
+ io.sleep(std.Io.Duration.fromSeconds(10), .awake) catch |err| switch (err) {
+ error.Canceled => {
+ print("Task was canceled, cleaning up.\n", .{});
+ return 0;
+ },
+ };
+
+ print("Task completed normally.\n", .{});
+ return 42;
+}
diff --git a/exercises/089_async6.zig b/exercises/089_async6.zig
deleted file mode 100644
index 8bf50e9..0000000
--- a/exercises/089_async6.zig
+++ /dev/null
@@ -1,54 +0,0 @@
-//
-// The power and purpose of async/await becomes more apparent
-// when we do multiple things concurrently. Foo and Bar do not
-// depend on each other and can happen at the same time, but End
-// requires that they both be finished.
-//
-// +---------+
-// | Start |
-// +---------+
-// / \
-// / \
-// +---------+ +---------+
-// | Foo | | Bar |
-// +---------+ +---------+
-// \ /
-// \ /
-// +---------+
-// | End |
-// +---------+
-//
-// We can express this in Zig like so:
-//
-// fn foo() u32 { ... }
-// fn bar() u32 { ... }
-//
-// // Start
-//
-// var foo_frame = async foo();
-// var bar_frame = async bar();
-//
-// var foo_value = await foo_frame;
-// var bar_value = await bar_frame;
-//
-// // End
-//
-// Please await TWO page titles!
-//
-const print = @import("std").debug.print;
-
-pub fn main() void {
- var com_frame = async getPageTitle("http://example.com");
- var org_frame = async getPageTitle("http://example.org");
-
- var com_title = com_frame;
- var org_title = org_frame;
-
- print(".com: {s}, .org: {s}.\n", .{ com_title, org_title });
-}
-
-fn getPageTitle(url: []const u8) []const u8 {
- // Please PRETEND this is actually making a network request.
- _ = url;
- return "Example Title";
-}
diff --git a/exercises/090_async6.zig b/exercises/090_async6.zig
new file mode 100644
index 0000000..16fb75f
--- /dev/null
+++ b/exercises/090_async6.zig
@@ -0,0 +1,76 @@
+//
+// Sometimes you want to race multiple tasks and act on whichever
+// finishes first. That's what Select is for!
+//
+// Select is like a Group, but lets you receive individual results
+// as tasks complete — one at a time:
+//
+// const Race = std.Io.Select(union(enum) {
+// fast: u32,
+// slow: u32,
+// });
+//
+// var buffer: [2]Race.Union = undefined;
+// var sel = Race.init(io, &buffer);
+//
+// sel.async(.fast, fastFn, .{io});
+// sel.async(.slow, slowFn, .{io});
+//
+// const winner = try sel.await(); // returns first completed
+// switch (winner) {
+// .fast => |val| ...,
+// .slow => |val| ...,
+// }
+// sel.cancelDiscard(); // cancel remaining, discard results
+//
+// As with all async primitives: tasks spawned in a Select MUST
+// be cleaned up. Use sel.cancel() to get remaining results one
+// by one (for resource cleanup), or sel.cancelDiscard() if you
+// don't need them.
+//
+// The buffer must be large enough for all tasks that might
+// complete before you call cancelDiscard().
+//
+// Fix this program to receive the winner of the race.
+//
+const std = @import("std");
+const print = std.debug.print;
+
+const RaceResult = std.Io.Select(union(enum) {
+ hare: []const u8,
+ tortoise: []const u8,
+});
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+
+ var buffer: [2]RaceResult.Union = undefined;
+ var sel = RaceResult.init(io, &buffer);
+
+ sel.async(.hare, runHare, .{io});
+ sel.async(.tortoise, runTortoise, .{io});
+
+ // Wait for the first finisher.
+ // What Select method returns the first completed result?
+ const winner = try sel.???();
+
+ switch (winner) {
+ .hare => |msg| print("Hare: {s}\n", .{msg}),
+ .tortoise => |msg| print("Tortoise: {s}\n", .{msg}),
+ }
+
+ // Clean up the loser — we don't need their result.
+ sel.cancelDiscard();
+}
+
+fn runHare(io: std.Io) []const u8 {
+ // The hare is fast — only 1 second!
+ io.sleep(std.Io.Duration.fromSeconds(1), .awake) catch return "I got canceled!";
+ return "I'm fast!";
+}
+
+fn runTortoise(io: std.Io) []const u8 {
+ // The tortoise is slow — 10 seconds.
+ io.sleep(std.Io.Duration.fromSeconds(10), .awake) catch return "I got canceled!";
+ return "Slow and steady...";
+}
diff --git a/exercises/090_async7.zig b/exercises/090_async7.zig
deleted file mode 100644
index 2da3a4a..0000000
--- a/exercises/090_async7.zig
+++ /dev/null
@@ -1,87 +0,0 @@
-//
-// Remember how a function with 'suspend' is async and calling an
-// async function without the 'async' keyword makes the CALLING
-// function async?
-//
-// fn fooThatMightSuspend(maybe: bool) void {
-// if (maybe) suspend {}
-// }
-//
-// fn bar() void {
-// fooThatMightSuspend(true); // Now bar() is async!
-// }
-//
-// But if you KNOW the function won't suspend, you can make a
-// promise to the compiler with the 'nosuspend' keyword:
-//
-// fn bar() void {
-// nosuspend fooThatMightSuspend(false);
-// }
-//
-// If the function does suspend and YOUR PROMISE TO THE COMPILER
-// IS BROKEN, the program will panic at runtime, which is
-// probably better than you deserve, you oathbreaker! >:-(
-//
-const print = @import("std").debug.print;
-
-pub fn main() void {
-
- // The main() function can not be async. But we know
- // getBeef() will not suspend with this particular
- // invocation. Please make this okay:
- var my_beef = getBeef(0);
-
- print("beef? {X}!\n", .{my_beef});
-}
-
-fn getBeef(input: u32) u32 {
- if (input == 0xDEAD) {
- suspend {}
- }
-
- return 0xBEEF;
-}
-//
-// Going Deeper Into...
-// ...uNdeFiNEd beHAVi0r!
-//
-// We haven't discussed it yet, but runtime "safety" features
-// require some extra instructions in your compiled program.
-// Most of the time, you're going to want to keep these in.
-//
-// But in some programs, when data integrity is less important
-// than raw speed (some games, for example), you can compile
-// without these safety features.
-//
-// Instead of a safe panic when something goes wrong, your
-// program will now exhibit Undefined Behavior (UB), which simply
-// means that the Zig language does not (cannot) define what will
-// happen. The best case is that it will crash, but in the worst
-// case, it will continue to run with the wrong results and
-// corrupt your data or expose you to security risks.
-//
-// This program is a great way to explore UB. Once you get it
-// working, try calling the getBeef() function with the value
-// 0xDEAD so that it will invoke the 'suspend' keyword:
-//
-// getBeef(0xDEAD)
-//
-// Now when you run the program, it will panic and give you a
-// nice stack trace to help debug the problem.
-//
-// zig run exercises/090_async7.zig
-// thread 328 panic: async function called...
-// ...
-//
-// But see what happens when you turn off safety checks by using
-// ReleaseFast mode:
-//
-// zig run -O ReleaseFast exercises/090_async7.zig
-// beef? 0!
-//
-// This is the wrong result. On your computer, you may get a
-// different answer or it might crash! What exactly will happen
-// is UNDEFINED. Your computer is now like a wild animal,
-// reacting to bits and bytes of raw memory with the base
-// instincts of the CPU. It is both terrifying and exhilarating.
-//
diff --git a/exercises/091_async7.zig b/exercises/091_async7.zig
new file mode 100644
index 0000000..bfe6ffd
--- /dev/null
+++ b/exercises/091_async7.zig
@@ -0,0 +1,57 @@
+//
+// When multiple async tasks access shared data, you need
+// synchronization! Io provides a Mutex for this:
+//
+// var mutex: std.Io.Mutex = .init;
+//
+// // In a task:
+// try mutex.lock(io); // blocks until lock is acquired
+// defer mutex.unlock();
+// // ... critical section: safe to modify shared data ...
+//
+// Without the mutex, concurrent tasks could read and write the
+// same memory simultaneously, causing a data race — the result
+// would be unpredictable.
+//
+// mutex.lock() is a cancellation point — it can return
+// error.Canceled. There's also tryLock() which returns
+// immediately (true if acquired, false if not).
+//
+// Fix this program so the counter is correctly synchronized.
+// Without the fix, the final count would be unpredictable.
+// With it, four tasks incrementing 100 times each = 400.
+//
+const std = @import("std");
+const print = std.debug.print;
+
+const SharedState = struct {
+ counter: u32 = 0,
+ mutex: std.Io.Mutex = .init,
+};
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+ var state = SharedState{};
+
+ var group: std.Io.Group = .init;
+
+ group.async(io, increment, .{ io, &state, 100 });
+ group.async(io, increment, .{ io, &state, 100 });
+ group.async(io, increment, .{ io, &state, 100 });
+ group.async(io, increment, .{ io, &state, 100 });
+
+ try group.await(io);
+
+ print("Counter: {}\n", .{state.counter});
+}
+
+fn increment(io: std.Io, state: *SharedState, times: u32) void {
+ for (0..times) |_| {
+ // Acquire the lock before modifying shared state.
+ // What Mutex method blocks until the lock is acquired?
+ state.mutex.??? catch return;
+ defer state.mutex.unlock(); // <-- what's missing here?
+
+ state.counter += 1;
+ }
+}
diff --git a/exercises/091_async8.zig b/exercises/091_async8.zig
deleted file mode 100644
index cd9c975..0000000
--- a/exercises/091_async8.zig
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// You have doubtless noticed that 'suspend' requires a block
-// expression like so:
-//
-// suspend {}
-//
-// The suspend block executes when a function suspends. To get
-// sense for when this happens, please make the following
-// program print the string
-//
-// "ABCDEF"
-//
-const print = @import("std").debug.print;
-
-pub fn main() void {
- print("A", .{});
-
- var frame = async suspendable();
-
- print("X", .{});
-
- resume frame;
-
- print("F", .{});
-}
-
-fn suspendable() void {
- print("X", .{});
-
- suspend {
- print("X", .{});
- }
-
- print("X", .{});
-}
diff --git a/exercises/092_async8.zig b/exercises/092_async8.zig
new file mode 100644
index 0000000..10921c3
--- /dev/null
+++ b/exercises/092_async8.zig
@@ -0,0 +1,62 @@
+//
+// Tasks often need to communicate! Io provides Queue for this —
+// a bounded, thread-safe channel for passing data between tasks:
+//
+// var backing: [16]u32 = undefined;
+// var queue: std.Io.Queue(u32) = .init(&backing);
+//
+// // Producer task:
+// try queue.putOne(io, value); // blocks if queue is full
+//
+// // Consumer task:
+// const val = try queue.getOne(io); // blocks if queue is empty
+//
+// When the producer is done, it calls queue.close(io) to signal
+// that no more data is coming. After that, getOne() will return
+// error.Closed once the queue is drained.
+//
+// This is the classic producer/consumer pattern — one task
+// generates work, another processes it, and the queue handles
+// all the synchronization automatically.
+//
+// Fix this program: the producer sends numbers 1..10, the
+// consumer sums them up. The expected sum is 55.
+//
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+
+ var backing: [4]u32 = undefined;
+ var queue: std.Io.Queue(u32) = .init(&backing);
+
+ var group: std.Io.Group = .init;
+
+ group.async(io, producer, .{ io, &queue });
+ group.async(io, consumer, .{ io, &queue });
+
+ try group.await(io);
+}
+
+fn producer(io: std.Io, queue: *std.Io.Queue(u32)) void {
+ // Send numbers 1 through 10 into the queue.
+ for (1..11) |i| {
+ // What Queue method sends a single element, blocking if full?
+ queue.???(io, @intCast(i)) catch return;
+ }
+ // Signal that we're done sending.
+ queue.close(io);
+}
+
+fn consumer(io: std.Io, queue: *std.Io.Queue(u32)) void {
+ var sum: u32 = 0;
+ while (true) {
+ const value = queue.getOne(io) catch |err| switch (err) {
+ error.Closed => break,
+ error.Canceled => return,
+ };
+ sum += value;
+ }
+ print("Sum of 1..10 = {}\n", .{sum});
+}
diff --git a/exercises/093_async9.zig b/exercises/093_async9.zig
new file mode 100644
index 0000000..4a41544
--- /dev/null
+++ b/exercises/093_async9.zig
@@ -0,0 +1,69 @@
+//
+// We've been using io.async() to launch tasks. But there's a
+// stronger variant: io.concurrent().
+//
+// The difference:
+//
+// io.async():
+// * The function MAY run on a separate unit of concurrency,
+// or it may run immediately on the caller (synchronously).
+// * Never fails — if no concurrency is available, it just
+// runs the function right away.
+// * More portable, works with all Io backends.
+//
+// io.concurrent():
+// * GUARANTEES a separate unit of concurrency.
+// * Can fail with error.ConcurrencyUnavailable if resources
+// are exhausted or the backend doesn't support it.
+// * Use when you NEED the task to run independently of the
+// caller.
+//
+// What is a "unit of concurrency"? That depends on the backend!
+// The Threaded backend uses OS threads. But the Evented backends
+// (Uring, Kqueue, Dispatch) use M:N green threads / fibers,
+// which can provide concurrency even on a SINGLE OS thread.
+// Your code doesn't need to know the difference.
+//
+// Because concurrent() can fail, you must handle the error:
+//
+// var future = try io.concurrent(myFn, .{args});
+// defer _ = future.cancel(io);
+// const result = future.await(io);
+//
+// Notice the 'try' — that's the key difference in usage!
+//
+// Fix this program to launch the computation concurrently.
+//
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+
+ // Launch with a guaranteed separate unit of concurrency.
+ // Which Io method guarantees this?
+ // (Hint: unlike io.async, this one can fail!)
+ var future = try io.???(compute, .{io});
+ defer _ = future.cancel(io);
+
+ // Note: All breaks in this excercise (using sleep)
+ // are only necessary for a deterministic result.
+ io.sleep(std.Io.Duration.fromMilliseconds(100), .awake) catch {};
+
+ print("Main continues...\n", .{});
+
+ // Wait 1 second for the output order.
+ io.sleep(std.Io.Duration.fromMilliseconds(200), .awake) catch {};
+
+ print("Main done waiting.\n", .{});
+
+ const result = future.await(io);
+ print("Result: {}\n", .{result});
+}
+
+fn compute(io: std.Io) u32 {
+ print("Computing concurrently!\n", .{});
+ // Simulate some work.
+ io.sleep(std.Io.Duration.fromMilliseconds(400), .awake) catch return 0;
+ return 123;
+}
diff --git a/exercises/094_async10.zig b/exercises/094_async10.zig
new file mode 100644
index 0000000..c561d37
--- /dev/null
+++ b/exercises/094_async10.zig
@@ -0,0 +1,68 @@
+//
+// In exercise 089, we learned that cancellation happens at
+// "cancellation points" — any Io function that can return
+// error.Canceled.
+//
+// But sometimes a task has a critical section that MUST NOT
+// be interrupted — for example, writing a consistent state
+// to disk, or completing a transaction.
+//
+// Io provides CancelProtection for this:
+//
+// const old = io.swapCancelProtection(.blocked);
+// defer _ = io.swapCancelProtection(old);
+
+// // In this block, NO Io function will return error.Canceled.
+// // The cancel request is held until protection is restored.
+//
+// There are two states:
+// .unblocked — normal: cancellation points can fire (default)
+// .blocked — protected: error.Canceled is never returned
+//
+// There's also io.checkCancel() — a pure cancellation point
+// that does nothing except return error.Canceled if a cancel
+// request is pending. Useful in long CPU-bound loops.
+//
+// And io.recancel() — re-arms a consumed cancel request so
+// the NEXT cancellation point will fire again.
+//
+// Fix this program so the critical section completes even
+// when the task is canceled.
+//
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+
+ var future = io.async(importantTask, .{io});
+ defer _ = future.cancel(io);
+
+ // Give the task time to start and enter its critical section.
+ io.sleep(std.Io.Duration.fromMilliseconds(200), .awake) catch {};
+
+ // Cancel while the task is in its protected section.
+ const result = future.cancel(io);
+ print("Task result: {s}\n", .{result});
+}
+
+fn importantTask(io: std.Io) []const u8 {
+ print("Starting critical section...\n", .{});
+
+ // Protect this section from cancellation.
+ // What method swaps the cancel protection state?
+ const old = io.???(.blocked);
+ defer _ = io.???(old);
+
+ // This sleep will NOT return error.Canceled even though
+ // we get canceled during it — protection is active!
+ io.sleep(std.Io.Duration.fromMilliseconds(300), .awake) catch |err| switch (err) {
+ error.Canceled => {
+ // This should never happen while protected!
+ return "ERROR: canceled during critical section!";
+ },
+ };
+
+ print("Critical section completed safely.\n", .{});
+ return "All data saved.";
+}
diff --git a/exercises/095_quiz_async.zig b/exercises/095_quiz_async.zig
new file mode 100644
index 0000000..b116fc9
--- /dev/null
+++ b/exercises/095_quiz_async.zig
@@ -0,0 +1,188 @@
+//
+// Quiz Time — Async I/O!
+//
+// Doctor Zoraptera's insect simulation is going well, but she
+// realized that her virtual garden needs weather data! Insects
+// behave differently depending on temperature, humidity, and
+// wind conditions.
+//
+// She has set up three weather sensors around the garden that
+// measure conditions in parallel and report their readings
+// through a shared data channel. A collector task gathers the
+// readings, and after all sensors have reported, a garden
+// report is printed.
+//
+// But Doctor Z rushed through the code (she was being chased
+// by a grasshopper) and left several bugs. Can you fix them?
+//
+// Here's what the program should do:
+// 1. Three sensor tasks send exactly 3 readings each through
+// a Queue
+// 2. A collector task receives readings concurrently,
+// protected by a Mutex
+// 3. After all sensors finish, the queue is closed
+// 4. The final report is written in a cancel-protected section
+//
+// *************************************************************
+// * A NOTE ABOUT THIS EXERCISE *
+// * *
+// * This quiz uses concepts from exercises 085-094. *
+// * There are 6 bugs to fix — look for the ???s! *
+// * *
+// *************************************************************
+//
+const std = @import("std");
+const print = std.debug.print;
+
+const SensorType = enum { thermometer, hygrometer, anemometer };
+
+const Reading = struct {
+ sensor_type: SensorType,
+ value: i32,
+};
+
+const GardenWeather = struct {
+ temperature: i32 = 0,
+ humidity: i32 = 0,
+ wind: i32 = 0,
+ readings_count: u32 = 0,
+ mutex: std.Io.Mutex = .init,
+
+ fn addReading(self: *GardenWeather, io: std.Io, reading: Reading) void {
+ // Bug 1: The collector needs to lock before modifying
+ // shared state. What Mutex method acquires the lock?
+ self.mutex.???(io) catch return;
+ defer self.mutex.unlock(io);
+
+ switch (reading.sensor_type) {
+ .thermometer => self.temperature = reading.value,
+ .hygrometer => self.humidity = reading.value,
+ .anemometer => self.wind = reading.value,
+ }
+ self.readings_count += 1;
+ }
+};
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+
+ var weather = GardenWeather{};
+
+ var reading_buf: [8]Reading = undefined;
+ var queue: std.Io.Queue(Reading) = .init(&reading_buf);
+
+ // The collector must run concurrently so it can process
+ // readings while the sensors are still sending.
+ // Start it FIRST to ensure its concurrency unit is reserved.
+ //
+ // Bug 2: The collector needs guaranteed concurrency.
+ // What method ensures a separate unit of concurrency?
+ // (Don't forget: it can fail!)
+ var collector_future = try io.???(collector, .{ io, &queue, &weather });
+ defer _ = collector_future.cancel(io);
+
+ // Sensor group: the sensors can use async — they just need
+ // to run, and async is more portable.
+ var sensors: std.Io.Group = .init;
+
+ sensors.async(io, sensor, .{ io, &queue, .thermometer, 20 });
+ sensors.async(io, sensor, .{ io, &queue, .hygrometer, 60 });
+ sensors.async(io, sensor, .{ io, &queue, .anemometer, 10 });
+
+ // Bug 3: Wait for ALL sensors to finish sending their readings.
+ // What Group method blocks until all tasks complete?
+ try sensors.???(io);
+
+ // All sensors done — close the queue so the collector knows
+ // there's no more data coming.
+ queue.close(io);
+
+ // Wait for the collector to drain the remaining queue.
+ _ = collector_future.await(io);
+ // _ = collector_future.???(io);
+
+ // Now write the garden report. This is critical — it must
+ // NOT be interrupted, even if something tries to cancel us!
+ //
+ // Bug 5: Protect this section from cancellation.
+ // What Io method swaps the cancel protection state?
+ const old_protection = io.???(.blocked);
+ defer _ = io.???(old_protection);
+
+ printGardenReport(&weather);
+}
+
+fn sensor(
+ io: std.Io,
+ queue: *std.Io.Queue(Reading),
+ sensor_type: SensorType,
+ base_value: i32,
+) void {
+ // Each sensor takes exactly 3 measurements.
+ for (1..4) |i| {
+ io.sleep(std.Io.Duration.fromMilliseconds(100), .awake) catch return;
+
+ const reading = Reading{
+ .sensor_type = sensor_type,
+ .value = base_value + @as(i32, @intCast(i)),
+ };
+
+ // Bug 6: Send the reading into the queue.
+ // What Queue method sends a single element?
+ queue.???(io, reading) catch return;
+ }
+}
+
+fn collector(
+ io: std.Io,
+ queue: *std.Io.Queue(Reading),
+ weather: *GardenWeather,
+) void {
+ while (true) {
+ const reading = queue.getOne(io) catch |err| switch (err) {
+ error.Closed => break,
+ error.Canceled => return,
+ };
+ weather.addReading(io, reading);
+ }
+}
+
+fn printGardenReport(weather: *GardenWeather) void {
+ print("=== Doctor Zoraptera's Garden Report ===\n", .{});
+ print("Temperature : {}C\n", .{weather.temperature});
+ print("Humidity : {}%\n", .{weather.humidity});
+ print("Wind : {} km/h\n", .{weather.wind});
+ print("Readings : {}\n", .{weather.readings_count});
+
+ if (weather.temperature > 20 and weather.wind < 15) {
+ print("Bee-friendly conditions! Expect high pollination.\n", .{});
+ } else {
+ print("Grasshoppers will be grumpy today.\n", .{});
+ }
+}
+
+// Further reading for the curious:
+//
+// This quiz covered the main async I/O primitives:
+// io.async() - launch a task (may run inline)
+// io.concurrent() - guaranteed unit of concurrency
+// Future.await/cancel - collect or cancel a single task
+// Group.async/await/cancel - manage fire-and-forget tasks
+// Select.async/await - race tasks, act on first completion
+// Queue - bounded channel between tasks
+// Mutex - protect shared state
+// CancelProtection - shield critical sections
+//
+// There are more synchronization primitives we didn't cover:
+// Condition - wait for a condition to become true
+// RwLock - multiple readers OR one writer
+// Semaphore - limit concurrent access to a resource
+// Futex - low-level wait/wake on a memory address
+// Batch - submit multiple I/O operations at once
+//
+// The key insight: all of these work through the Io VTable,
+// so your code is portable across backends — whether Threaded
+// (OS thread pool), or Evented (M:N green threads / fibers
+// that can provide concurrency even on a single OS thread).
+//
+// Doctor Zoraptera approves.
diff --git a/exercises/093_hello_c.zig b/exercises/096_hello_c.zig
index 182e0b0..182e0b0 100644
--- a/exercises/093_hello_c.zig
+++ b/exercises/096_hello_c.zig
diff --git a/exercises/094_c_math.zig b/exercises/097_c_math.zig
index ec59a86..ec59a86 100644
--- a/exercises/094_c_math.zig
+++ b/exercises/097_c_math.zig
diff --git a/exercises/095_for3.zig b/exercises/098_for3.zig
index 77a1b56..77a1b56 100644
--- a/exercises/095_for3.zig
+++ b/exercises/098_for3.zig
diff --git a/exercises/096_memory_allocation.zig b/exercises/099_memory_allocation.zig
index 58de7b0..58de7b0 100644
--- a/exercises/096_memory_allocation.zig
+++ b/exercises/099_memory_allocation.zig
diff --git a/exercises/097_bit_manipulation.zig b/exercises/100_bit_manipulation.zig
index 0e64ad7..0e64ad7 100644
--- a/exercises/097_bit_manipulation.zig
+++ b/exercises/100_bit_manipulation.zig
diff --git a/exercises/098_bit_manipulation2.zig b/exercises/101_bit_manipulation2.zig
index 8b51265..8b51265 100644
--- a/exercises/098_bit_manipulation2.zig
+++ b/exercises/101_bit_manipulation2.zig
diff --git a/exercises/099_formatting.zig b/exercises/102_formatting.zig
index be16978..be16978 100644
--- a/exercises/099_formatting.zig
+++ b/exercises/102_formatting.zig
diff --git a/exercises/100_for4.zig b/exercises/103_for4.zig
index c8a1161..c8a1161 100644
--- a/exercises/100_for4.zig
+++ b/exercises/103_for4.zig
diff --git a/exercises/101_for5.zig b/exercises/104_for5.zig
index 200e71d..200e71d 100644
--- a/exercises/101_for5.zig
+++ b/exercises/104_for5.zig
diff --git a/exercises/102_testing.zig b/exercises/105_testing.zig
index 0dec9b9..0dec9b9 100644
--- a/exercises/102_testing.zig
+++ b/exercises/105_testing.zig
diff --git a/exercises/103_tokenization.zig b/exercises/106_tokenization.zig
index d0abb5c..d0abb5c 100644
--- a/exercises/103_tokenization.zig
+++ b/exercises/106_tokenization.zig
diff --git a/exercises/104_threading.zig b/exercises/107_threading.zig
index 2b0e6f7..3c3fa21 100644
--- a/exercises/104_threading.zig
+++ b/exercises/107_threading.zig
@@ -1,31 +1,22 @@
//
-// Whenever there is a lot to calculate, the question arises as to how
-// tasks can be carried out simultaneously. We have already learned about
-// one possibility, namely asynchronous processes, in Exercises 84-91.
+// In Exercises 84-91, we learned about Zig's Io interface for
+// concurrent execution: io.async(), Group, Select, and Futures.
+// Under the hood, the Threaded backend manages a pool of real
+// OS threads for you - including scheduling, cancellation, and
+// resource cleanup.
//
-// However, the computing power of the processor is only distributed to
-// the started and running tasks, which always reaches its limits when
-// pure computing power is called up.
+// But sometimes you need direct control over threads:
+// * Long-lived dedicated workers
+// * Specific stack sizes or thread counts
+// * Code that doesn't have an Io interface available
+// * Fine-grained synchronization patterns
//
-// For example, in blockchains based on proof of work, the miners have
-// to find a nonce for a certain character string so that the first m bits
-// in the hash of the character string and the nonce are zeros.
-// As the miner who can solve the task first receives the reward, everyone
-// tries to complete the calculations as quickly as possible.
+// That's where std.Thread comes in. It gives you a raw OS thread
+// that you spawn, manage, and join yourself. No pool, no Futures,
+// no automatic cancellation - but full control.
//
-// This is where multithreading comes into play, where tasks are actually
-// distributed across several cores of the CPU or GPU, which then really
-// means a multiplication of performance.
-//
-// The following diagram roughly illustrates the difference between the
-// various types of process execution.
-// The 'Overall Time' column is intended to illustrate how the time is
-// affected if, instead of one core as in synchronous and asynchronous
-// processing, a second core now helps to complete the work in multithreading.
-//
-// In the ideal case shown, execution takes only half the time compared
-// to the synchronous single thread. And even asynchronous processing
-// is only slightly faster in comparison.
+// The following diagram roughly illustrates the difference between
+// the various types of process execution:
//
//
// Synchronous Asynchronous
@@ -108,7 +99,7 @@ pub fn main() !void {
// they run in parallel and we can still do some work in between.
var io_instance: std.Io.Threaded = .init_single_threaded;
const io = io_instance.io();
- try io.sleep(std.Io.Duration.fromSeconds(4), .awake);
+ try io.sleep(std.Io.Duration.fromMilliseconds(400), .awake);
std.debug.print("Some weird stuff, after starting the threads.\n", .{});
}
// After we have left the closed area, we wait until
@@ -118,17 +109,17 @@ pub fn main() !void {
// This function is started with every thread that we set up.
// In our example, we pass the number of the thread as a parameter.
-fn thread_function(num: usize) !void {
+fn thread_function(id: usize) !void {
var io_instance: std.Io.Threaded = .init_single_threaded;
const io = io_instance.io();
- try io.sleep(std.Io.Duration.fromSeconds(1 * @as(isize, @intCast(num))), .awake);
- std.debug.print("thread {d}: {s}\n", .{ num, "started." });
+ try io.sleep(std.Io.Duration.fromMilliseconds(100 * @as(isize, @intCast(id))), .awake);
+ std.debug.print("thread {d}: {s}\n", .{ id, "started." });
// This timer simulates the work of the thread.
- const work_time = 3 * ((5 - num % 3) - 2);
- try io.sleep(std.Io.Duration.fromSeconds(@intCast(work_time)), .awake);
+ const work_time = 300 * ((5 - id % 3) - 2);
+ try io.sleep(std.Io.Duration.fromMilliseconds(@intCast(work_time)), .awake);
- std.debug.print("thread {d}: {s}\n", .{ num, "finished." });
+ std.debug.print("thread {d}: {s}\n", .{ id, "finished." });
}
// This is the easiest way to run threads in parallel.
// In general, however, more management effort is required,
diff --git a/exercises/105_threading2.zig b/exercises/108_threading2.zig
index 374391a..374391a 100644
--- a/exercises/105_threading2.zig
+++ b/exercises/108_threading2.zig
diff --git a/exercises/106_files.zig b/exercises/109_files.zig
index bf4de9b..bf4de9b 100644
--- a/exercises/106_files.zig
+++ b/exercises/109_files.zig
diff --git a/exercises/107_files2.zig b/exercises/110_files2.zig
index c363086..c363086 100644
--- a/exercises/107_files2.zig
+++ b/exercises/110_files2.zig
diff --git a/exercises/108_labeled_switch.zig b/exercises/111_labeled_switch.zig
index 897fcf5..897fcf5 100644
--- a/exercises/108_labeled_switch.zig
+++ b/exercises/111_labeled_switch.zig
diff --git a/exercises/109_vectors.zig b/exercises/112_vectors.zig
index 96892ca..96892ca 100644
--- a/exercises/109_vectors.zig
+++ b/exercises/112_vectors.zig
diff --git a/exercises/110_quiz9.zig b/exercises/113_quiz9.zig
index 8f5cb61..8f5cb61 100644
--- a/exercises/110_quiz9.zig
+++ b/exercises/113_quiz9.zig
diff --git a/exercises/111_packed.zig b/exercises/114_packed.zig
index 1058f76..1058f76 100644
--- a/exercises/111_packed.zig
+++ b/exercises/114_packed.zig
diff --git a/exercises/112_packed2.zig b/exercises/115_packed2.zig
index 2b6b558..2b6b558 100644
--- a/exercises/112_packed2.zig
+++ b/exercises/115_packed2.zig