From 77d3b684cb467ad4e06c211518e2d2d1c7346ad1 Mon Sep 17 00:00:00 2001
From: Chris Boesch
Date: Wed, 1 Apr 2026 22:28:37 +0200
Subject: revival of the async-io functions
---
build.zig | 71 ++++++++++++++++++++++++++++++++++++++++++---------------------
1 file changed, 47 insertions(+), 24 deletions(-)
(limited to 'build.zig')
diff --git a/build.zig b/build.zig
index 968a6b0..fa0dcac 100644
--- a/build.zig
+++ b/build.zig
@@ -75,6 +75,8 @@ pub const Exercise = struct {
/// Hint to the user, why this has been skipped
skip_hint: ?[]const u8 = null,
+ timestamp: bool = false,
+
/// Returns the name of the main file with .zig stripped.
pub fn name(self: Exercise) []const u8 {
return std.fs.path.stem(self.main_file);
@@ -435,11 +437,30 @@ const ZiglingStep = struct {
result.stderr;
// Validate the output.
+ var exercise_output = self.exercise.output;
+
+ // Insert timestamp for exercise 84
+ if (self.exercise.timestamp) {
+ var ts_buf: [20]u8 = undefined;
+ const timestamp = std.fmt.bufPrint(&ts_buf, "{}", .{std.Io.Timestamp.now(io, .real).toSeconds()}) catch unreachable;
+
+ var buf: [100]u8 = undefined;
+ const prefix_len = 14;
+ const placeholder_len = 11;
+ @memcpy(buf[0..prefix_len], exercise_output[0..prefix_len]);
+ @memcpy(buf[prefix_len..][0..timestamp.len], timestamp);
+ const suffix = exercise_output[prefix_len + placeholder_len ..];
+ const suffix_dest_start = prefix_len + timestamp.len;
+ @memcpy(buf[suffix_dest_start..][0..suffix.len], suffix);
+
+ const total_len = prefix_len + timestamp.len + suffix.len;
+ exercise_output = buf[0..total_len];
+ }
+
// NOTE: exercise.output can never contain a CR character.
// See https://ziglang.org/documentation/master/#Source-Encoding.
const output = trimLines(b.allocator, raw_output) catch @panic("OOM");
- const exercise_output = self.exercise.output;
- if (!std.mem.eql(u8, output, self.exercise.output)) {
+ if (!std.mem.eql(u8, output, exercise_output)) {
const red = red_bold_text;
const reset = reset_text;
@@ -698,7 +719,7 @@ const exercises = [_]Exercise{
\\most part, you'll be taking directions from the Zig
\\compiler itself.)
\\
- ,
+ , // pay attention to the comma
},
.{
.main_file = "002_std.zig",
@@ -730,7 +751,7 @@ const exercises = [_]Exercise{
\\Ziggy played guitar
\\Jamming good with Andrew Kelley
\\And the Spiders from Mars
- ,
+ , // pay attention to the comma
.hint = "Please fix the lyrics!",
},
.{
@@ -867,7 +888,7 @@ const exercises = [_]Exercise{
\\ Green
\\ Blue
\\
- ,
+ , // pay attention to the comma
.hint = "I'm feeling blue about this.",
},
.{
@@ -879,7 +900,7 @@ const exercises = [_]Exercise{
.output =
\\Character 1 - G:20 H:100 XP:10
\\Character 2 - G:10 H:100 XP:20
- ,
+ , // pay attention to the comma
},
.{
.main_file = "039_pointers.zig",
@@ -903,7 +924,7 @@ const exercises = [_]Exercise{
.output =
\\Wizard (G:10 H:100 XP:20)
\\ Mentor: Wizard (G:10000 H:100 XP:2340)
- ,
+ , // pay attention to the comma
},
.{
.main_file = "044_quiz5.zig",
@@ -947,7 +968,7 @@ const exercises = [_]Exercise{
.output =
\\Hand1: A 4 K 8
\\Hand2: 5 2 Q J
- ,
+ , // pay attention to the comma
},
.{
.main_file = "053_slices2.zig",
@@ -1043,7 +1064,10 @@ const exercises = [_]Exercise{
.main_file = "073_comptime8.zig",
.output = "My llama value is 25.",
},
- .{ .main_file = "074_comptime9.zig", .output = "My llama value is 2.", .skip = false, .skip_hint = "This is actually correct as it is. :-)" },
+ .{
+ .main_file = "074_comptime9.zig",
+ .output = "My llama value is 2.",
+ },
.{
.main_file = "075_quiz8.zig",
.output = "Archer's Point--2->Bridge--1->Dogwood Grove--3->Cottage--2->East Pond--1->Fox Pond",
@@ -1078,7 +1102,7 @@ const exercises = [_]Exercise{
.main_file = "082_anonymous_structs3.zig",
.output =
\\"0"(bool):true "1"(bool):false "2"(i32):42 "3"(f32):3.141592
- ,
+ , // pay attention to the comma
.hint = "This one is a challenge! But you have everything you need.",
},
.{
@@ -1090,16 +1114,15 @@ const exercises = [_]Exercise{
// direct link: https://github.com/ziglang/zig/issues/6025
.{
.main_file = "084_async.zig",
- .output = "foo() A",
- .hint = "Read the facts. Use the facts.",
- .skip = true,
- .skip_hint = "async has not been implemented in the current compiler version.",
+ .output = "Current time: s since epoch",
+ .timestamp = true,
+ // .hint = "Read the facts. Use the facts.",
+ // .skip = true,
+ // .skip_hint = "async has not been implemented in the current compiler version.",
},
.{
.main_file = "085_async2.zig",
- .output = "Hello async!",
- .skip = true,
- .skip_hint = "async has not been implemented in the current compiler version.",
+ .output = "Computing... the answer is: 42",
},
.{
.main_file = "086_async3.zig",
@@ -1145,7 +1168,7 @@ const exercises = [_]Exercise{
\\Ant is alive.
\\Bee visited 17 flowers.
\\Grasshopper hopped 32 meters.
- ,
+ , // pay attention to the comma
},
.{
.main_file = "093_hello_c.zig",
@@ -1221,7 +1244,7 @@ const exercises = [_]Exercise{
\\2. Bard (Gold: 11, XP: 17)
\\3. Bard (Gold: 5, XP: 55)
\\4. Warrior (Gold: 7392, XP: 21)
- ,
+ , // pay attention to the comma
},
.{
.main_file = "102_testing.zig",
@@ -1247,7 +1270,7 @@ const exercises = [_]Exercise{
\\and
\\despair
\\This little poem has 15 words!
- ,
+ , // pay attention to the comma
},
.{
.main_file = "104_threading.zig",
@@ -1261,7 +1284,7 @@ const exercises = [_]Exercise{
\\thread 1: finished.
\\thread 3: finished.
\\Zig is cool!
- ,
+ , // pay attention to the comma
},
.{
.main_file = "105_threading2.zig",
@@ -1276,7 +1299,7 @@ const exercises = [_]Exercise{
.output =
\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
\\Successfully Read 18 bytes: It's zigling time!
- ,
+ , // pay attention to the comma
},
.{
.main_file = "108_labeled_switch.zig",
@@ -1287,7 +1310,7 @@ const exercises = [_]Exercise{
.output =
\\Max difference (old fn): 0.014
\\Max difference (new fn): 0.014
- ,
+ , // pay attention to the comma
},
.{ .main_file = "110_quiz9.zig", .output =
\\Toggle pins with XOR on PORTB
@@ -1334,6 +1357,6 @@ const exercises = [_]Exercise{
\\
\\This is the end for now!
\\We hope you had fun and were able to learn a lot, so visit us again when the next exercises are available.
- ,
+ , // pay attention to the comma
},
};
--
cgit v1.2.3
From db1fef8b864f1af6f9d2b8a7871e72d77747304d Mon Sep 17 00:00:00 2001
From: Chris Boesch
Date: Wed, 1 Apr 2026 22:52:04 +0200
Subject: revival of the async-io functions
---
build.zig | 11 ++++----
exercises/086_async3.zig | 59 +++++++++++++++++++++++++++-------------
patches/patches/086_async3.patch | 18 ++++++++++++
3 files changed, 63 insertions(+), 25 deletions(-)
create mode 100644 patches/patches/086_async3.patch
(limited to 'build.zig')
diff --git a/build.zig b/build.zig
index fa0dcac..d2ee3aa 100644
--- a/build.zig
+++ b/build.zig
@@ -1116,9 +1116,6 @@ const exercises = [_]Exercise{
.main_file = "084_async.zig",
.output = "Current time: s since epoch",
.timestamp = true,
- // .hint = "Read the facts. Use the facts.",
- // .skip = true,
- // .skip_hint = "async has not been implemented in the current compiler version.",
},
.{
.main_file = "085_async2.zig",
@@ -1126,9 +1123,11 @@ const exercises = [_]Exercise{
},
.{
.main_file = "086_async3.zig",
- .output = "5 4 3 2 1",
- .skip = true,
- .skip_hint = "async has not been implemented in the current compiler version.",
+ .output =
+ \\1 + 2 = 3
+ \\6 * 7 = 42
+ \\Total: 45
+ , // pay attention to the comma
},
.{
.main_file = "087_async4.zig",
diff --git a/exercises/086_async3.zig b/exercises/086_async3.zig
index c8f1113..07221e9 100644
--- a/exercises/086_async3.zig
+++ b/exercises/086_async3.zig
@@ -1,29 +1,50 @@
//
-// 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.
+// The real power of async shows when you launch MULTIPLE tasks!
//
-// See if you can make this program print "5 4 3 2 1".
+// With io.async(), you can start several operations, then await
+// them all. The Io backend may run them concurrently:
//
-const print = @import("std").debug.print;
+// var f1 = io.async(taskA, .{});
+// var f2 = io.async(taskB, .{});
+//
+// // Both tasks may be running now!
+// const a = f1.await(io);
+// const b = f2.await(io);
+//
+// There's also io.concurrent() which provides a STRONGER guarantee:
+// it ensures the function gets its own unit of concurrency (e.g. a
+// real OS thread). But it can fail with error.ConcurrencyUnavailable
+// if resources are exhausted.
+//
+// io.async() is more portable: if no thread is available, it simply
+// runs the function synchronously. This makes it the right default
+// for most code.
+//
+// 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;
-pub fn main() void {
- const n = 5;
- var foo_frame = async foo(n);
+ // Launch both tasks asynchronously.
+ var future_a = io.async(slowAdd, .{ 10, 20 });
+ var future_b = ???(slowMul, .{ 6, 7 });
- ???
+ // Await both results.
+ const sum = future_a.await(io);
+ const product = future_b.???(io);
- print("\n", .{});
+ print("{} + {} = {}\n", .{ 1, 2, sum });
+ print("{} * {} = {}\n", .{ 6, 7, product });
+ print("Total: {}\n", .{sum + product});
}
-fn foo(countdown: u32) void {
- var current = countdown;
+fn slowAdd(a: u32, b: u32) u32 {
+ return a + b;
+}
- while (current > 0) {
- print("{} ", .{current});
- current -= 1;
- suspend {}
- }
+fn slowMul(a: u32, b: u32) u32 {
+ return a * b;
}
diff --git a/patches/patches/086_async3.patch b/patches/patches/086_async3.patch
new file mode 100644
index 0000000..3a3c2c4
--- /dev/null
+++ b/patches/patches/086_async3.patch
@@ -0,0 +1,18 @@
+--- exercises/086_async3.zig 2026-04-01 22:51:05.540094851 +0200
++++ answers/086_async3.zig 2026-04-01 22:50:44.579669189 +0200
+@@ -29,12 +29,12 @@
+ const io = init.io;
+
+ // Launch both tasks asynchronously.
+- var future_a = io.async(slowAdd, .{ 10, 20 });
+- var future_b = ???(slowMul, .{ 6, 7 });
++ var future_a = io.async(slowAdd, .{ 1, 2 });
++ var future_b = io.async(slowMul, .{ 6, 7 });
+
+ // Await both results.
+ const sum = future_a.await(io);
+- const product = future_b.???(io);
++ const product = future_b.await(io);
+
+ print("{} + {} = {}\n", .{ 1, 2, sum });
+ print("{} * {} = {}\n", .{ 6, 7, product });
--
cgit v1.2.3
From fcfb0e80a039d8283f6a298ed67a26c23e3a560a Mon Sep 17 00:00:00 2001
From: Chris Boesch
Date: Wed, 1 Apr 2026 23:34:16 +0200
Subject: revival of the async-io functions
---
build.zig | 9 ++++--
exercises/087_async4.zig | 58 +++++++++++++++++++++++++------------
exercises/104_threading.zig | 53 ++++++++++++++-------------------
patches/patches/087_async4.patch | 11 +++++++
patches/patches/104_threading.patch | 6 ++--
5 files changed, 81 insertions(+), 56 deletions(-)
create mode 100644 patches/patches/087_async4.patch
(limited to 'build.zig')
diff --git a/build.zig b/build.zig
index d2ee3aa..1d40dec 100644
--- a/build.zig
+++ b/build.zig
@@ -1131,9 +1131,12 @@ const exercises = [_]Exercise{
},
.{
.main_file = "087_async4.zig",
- .output = "1 2 3 4 5",
- .skip = true,
- .skip_hint = "async has not been implemented in the current compiler version.",
+ .output =
+ \\Task 1 done.
+ \\Task 2 done.
+ \\Task 3 done.
+ \\All tasks finished!
+ , // pay attention to the comma
},
.{
.main_file = "088_async5.zig",
diff --git a/exercises/087_async4.zig b/exercises/087_async4.zig
index bb9c9ec..50829fc 100644
--- a/exercises/087_async4.zig
+++ b/exercises/087_async4.zig
@@ -1,30 +1,50 @@
//
-// 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.
+// 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:
//
-// One way to solve this is to use a global variable.
+// 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
//
-// See if you can make this program print "1 2 3 4 5".
+// 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 waits for them to finish.
//
-const print = @import("std").debug.print;
+// 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 global_counter: i32 = 0;
+ var group: std.Io.Group = .init;
-pub fn main() void {
- var foo_frame = async foo();
+ // 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 });
- while (global_counter <= 5) {
- print("{} ", .{global_counter});
- ???
- }
+ // Wait for all tasks to finish.
+ // What Group method blocks until all tasks complete?
+ try group.???
- print("\n", .{});
+ print("All tasks finished!\n", .{});
}
-fn foo() void {
- while (true) {
- ???
- ???
- }
+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/104_threading.zig b/exercises/104_threading.zig
index 2b0e6f7..3c3fa21 100644
--- a/exercises/104_threading.zig
+++ b/exercises/104_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/patches/patches/087_async4.patch b/patches/patches/087_async4.patch
new file mode 100644
index 0000000..4883afd
--- /dev/null
+++ b/patches/patches/087_async4.patch
@@ -0,0 +1,11 @@
+--- exercises/087_async4.zig 2026-04-01 23:17:31.066443941 +0200
++++ answers/087_async4.zig 2026-04-01 23:17:39.251612131 +0200
+@@ -38,7 +38,7 @@
+
+ // Wait for all tasks to finish.
+ // What Group method blocks until all tasks complete?
+- try group.???
++ try group.await(io);
+
+ print("All tasks finished!\n", .{});
+ }
diff --git a/patches/patches/104_threading.patch b/patches/patches/104_threading.patch
index eda25fd..e6fe0f4 100644
--- a/patches/patches/104_threading.patch
+++ b/patches/patches/104_threading.patch
@@ -1,6 +1,6 @@
---- exercises/104_threading.zig 2025-11-28 14:17:31.552529679 +0100
-+++ answers/104_threading.zig 2025-11-28 14:15:36.823931851 +0100
-@@ -97,12 +97,12 @@
+--- exercises/104_threading.zig 2026-04-01 23:31:10.073198955 +0200
++++ answers/104_threading.zig 2026-04-01 23:29:51.314585919 +0200
+@@ -88,12 +88,12 @@
defer handle.join();
// Second thread
--
cgit v1.2.3
From e22748d48899d93c6af2684b33688c91c239596c Mon Sep 17 00:00:00 2001
From: Chris Boesch
Date: Wed, 1 Apr 2026 23:44:24 +0200
Subject: revival of the async-io functions
---
README.md | 2 +-
build.zig | 9 +++--
exercises/088_async5.zig | 87 +++++++++++++++++++++++-----------------
patches/patches/088_async5.patch | 11 +++++
4 files changed, 68 insertions(+), 41 deletions(-)
create mode 100644 patches/patches/088_async5.patch
(limited to 'build.zig')
diff --git a/README.md b/README.md
index 8d90e03..35eadd4 100644
--- a/README.md
+++ b/README.md
@@ -193,7 +193,7 @@ Zig Core Language
* [x] Sentinel termination
* [x] Quoted identifiers @""
* [x] Anonymous structs/tuples/lists
-* [ ] Async I/O
+* [x] Async I/O
* [X] Interfaces
* [X] Bit manipulation
* [X] Working with C
diff --git a/build.zig b/build.zig
index 1d40dec..d0f4b52 100644
--- a/build.zig
+++ b/build.zig
@@ -1140,9 +1140,12 @@ const exercises = [_]Exercise{
},
.{
.main_file = "088_async5.zig",
- .output = "Example Title.",
- .skip = true,
- .skip_hint = "async has not been implemented in the current compiler version.",
+ .output =
+ \\Starting long computation...
+ \\Canceling slow task...
+ \\Task was canceled, cleaning up.
+ \\Task returned: 0
+ , // pay attention to the comma
},
.{
.main_file = "089_async6.zig",
diff --git a/exercises/088_async5.zig b/exercises/088_async5.zig
index 759a920..4fb8d76 100644
--- a/exercises/088_async5.zig
+++ b/exercises/088_async5.zig
@@ -1,48 +1,61 @@
//
-// 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;
+// 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. Waits for the task to actually finish
+// 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, // handle gracefully
+// };
+// return 42;
// }
//
-// 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')
+// This is fundamentally different from killing a thread -
+// the task gets a chance to clean up and return a value!
//
-// Also notice that a 'suspend' keyword does NOT need to exist in
-// a function to be called in an async context.
+// 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.
//
-// Please use 'await' to get the string returned by
-// getPageTitle().
-//
-const print = @import("std").debug.print;
+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});
-pub fn main() void {
- var myframe = async getPageTitle("http://example.com");
+ // Wait 1 second, then cancel instead of waiting the full 10.
+ io.sleep(std.Io.Duration.fromSeconds(1), .awake) catch {};
- var value = ???
+ print("Canceling slow task...\n", .{});
- print("{s}\n", .{value});
+ // We don't want to wait 10 seconds!
+ // Which Future method requests cancellation AND returns the result?
+ const result = ???;
+
+ print("Task returned: {}\n", .{result});
}
-fn getPageTitle(url: []const u8) []const u8 {
- // Please PRETEND this is actually making a network request.
- _ = url;
- return "Example Title.";
+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/patches/patches/088_async5.patch b/patches/patches/088_async5.patch
new file mode 100644
index 0000000..d88e820
--- /dev/null
+++ b/patches/patches/088_async5.patch
@@ -0,0 +1,11 @@
+--- exercises/088_async5.zig 2026-04-01 23:40:40.505855238 +0200
++++ answers/088_async5.zig 2026-04-01 23:40:10.176236971 +0200
+@@ -40,7 +40,7 @@
+
+ // We don't want to wait 10 seconds!
+ // Which Future method requests cancellation AND returns the result?
+- const result = ???;
++ const result = future.cancel(io);
+
+ print("Task returned: {}\n", .{result});
+ }
--
cgit v1.2.3
From ffde357f303e7459a12cfe4b785ae9e8ef9ebe30 Mon Sep 17 00:00:00 2001
From: Chris Boesch
Date: Thu, 2 Apr 2026 10:38:45 +0200
Subject: revival of the async-io functions, #90
---
build.zig | 8 +--
exercises/090_async7.zig | 116 +++++++++++++++------------------------
patches/patches/090_async7.patch | 13 +++++
3 files changed, 58 insertions(+), 79 deletions(-)
create mode 100644 patches/patches/090_async7.patch
(limited to 'build.zig')
diff --git a/build.zig b/build.zig
index d0f4b52..3dc0520 100644
--- a/build.zig
+++ b/build.zig
@@ -1149,15 +1149,11 @@ const exercises = [_]Exercise{
},
.{
.main_file = "089_async6.zig",
- .output = ".com: Example Title, .org: Example Title.",
- .skip = true,
- .skip_hint = "async has not been implemented in the current compiler version.",
+ .output = "Hare: I'm fast!",
},
.{
.main_file = "090_async7.zig",
- .output = "beef? BEEF!",
- .skip = true,
- .skip_hint = "async has not been implemented in the current compiler version.",
+ .output = "Counter: 400 (expected: 400)",
},
.{
.main_file = "091_async8.zig",
diff --git a/exercises/090_async7.zig b/exercises/090_async7.zig
index 2da3a4a..f914aef 100644
--- a/exercises/090_async7.zig
+++ b/exercises/090_async7.zig
@@ -1,87 +1,57 @@
//
-// Remember how a function with 'suspend' is async and calling an
-// async function without the 'async' keyword makes the CALLING
-// function async?
+// When multiple async tasks access shared data, you need
+// synchronization! Io provides a Mutex for this:
//
-// fn fooThatMightSuspend(maybe: bool) void {
-// if (maybe) suspend {}
-// }
+// var mutex: std.Io.Mutex = .init;
//
-// fn bar() void {
-// fooThatMightSuspend(true); // Now bar() is async!
-// }
+// // In a task:
+// try mutex.lock(io); // blocks until lock is acquired
+// defer mutex.unlock();
+// // ... critical section: safe to modify shared data ...
//
-// But if you KNOW the function won't suspend, you can make a
-// promise to the compiler with the 'nosuspend' keyword:
+// Without the mutex, concurrent tasks could read and write the
+// same memory simultaneously, causing a data race — the result
+// would be unpredictable.
//
-// fn bar() void {
-// nosuspend fooThatMightSuspend(false);
-// }
+// mutex.lock() is a cancellation point — it can return
+// error.Canceled. There's also tryLock() which returns
+// immediately (true if acquired, false if not).
//
-// 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! >:-(
+// 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 print = @import("std").debug.print;
+const std = @import("std");
+const print = std.debug.print;
-pub fn main() void {
+const SharedState = struct {
+ counter: u32 = 0,
+ mutex: std.Io.Mutex = .init,
+};
- // 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);
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+ var state = SharedState{};
- print("beef? {X}!\n", .{my_beef});
+ 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: {} (expected: 400)\n", .{state.counter});
}
-fn getBeef(input: u32) u32 {
- if (input == 0xDEAD) {
- suspend {}
- }
+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?
- return 0xBEEF;
+ state.counter += 1;
+ }
}
-//
-// 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/patches/patches/090_async7.patch b/patches/patches/090_async7.patch
new file mode 100644
index 0000000..2108d51
--- /dev/null
+++ b/patches/patches/090_async7.patch
@@ -0,0 +1,13 @@
+--- exercises/090_async7.zig 2026-04-02 10:36:42.910708919 +0200
++++ answers/090_async7.zig 2026-04-02 10:36:51.965884223 +0200
+@@ -49,8 +49,8 @@
+ 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.mutex.lock(io) catch return;
++ defer state.mutex.unlock(io);
+
+ state.counter += 1;
+ }
--
cgit v1.2.3
From e0259f43a726f61da14686de802021fcdb9aacd0 Mon Sep 17 00:00:00 2001
From: Chris Boesch
Date: Fri, 3 Apr 2026 13:35:56 +0200
Subject: Insert space for additional async exercises
---
build.zig | 62 ++--
exercises/090_async7.zig | 2 +-
exercises/091_async8.zig | 67 ++--
exercises/092_interfaces.zig | 127 --------
exercises/093_hello_c.zig | 68 ----
exercises/094_c_math.zig | 41 ---
exercises/095_for3.zig | 81 -----
exercises/095_interfaces.zig | 127 ++++++++
exercises/096_hello_c.zig | 68 ++++
exercises/096_memory_allocation.zig | 78 -----
exercises/097_bit_manipulation.zig | 95 ------
exercises/097_c_math.zig | 41 +++
exercises/098_bit_manipulation2.zig | 64 ----
exercises/098_for3.zig | 81 +++++
exercises/099_formatting.zig | 140 --------
exercises/099_memory_allocation.zig | 78 +++++
exercises/100_bit_manipulation.zig | 95 ++++++
exercises/100_for4.zig | 62 ----
exercises/101_bit_manipulation2.zig | 64 ++++
exercises/101_for5.zig | 126 --------
exercises/102_formatting.zig | 140 ++++++++
exercises/102_testing.zig | 96 ------
exercises/103_for4.zig | 62 ++++
exercises/103_tokenization.zig | 148 ---------
exercises/104_for5.zig | 126 ++++++++
exercises/104_threading.zig | 129 --------
exercises/105_testing.zig | 96 ++++++
exercises/105_threading2.zig | 107 ------
exercises/106_files.zig | 102 ------
exercises/106_tokenization.zig | 148 +++++++++
exercises/107_files2.zig | 61 ----
exercises/107_threading.zig | 129 ++++++++
exercises/108_labeled_switch.zig | 79 -----
exercises/108_threading2.zig | 107 ++++++
exercises/109_files.zig | 102 ++++++
exercises/109_vectors.zig | 147 ---------
exercises/110_files2.zig | 61 ++++
exercises/110_quiz9.zig | 484 ----------------------------
exercises/111_labeled_switch.zig | 79 +++++
exercises/111_packed.zig | 175 ----------
exercises/112_packed2.zig | 78 -----
exercises/112_vectors.zig | 147 +++++++++
exercises/113_quiz9.zig | 484 ++++++++++++++++++++++++++++
exercises/114_packed.zig | 175 ++++++++++
exercises/115_packed2.zig | 78 +++++
patches/patches/091_async8.patch | 11 +
patches/patches/092_interfaces.patch | 11 -
patches/patches/093_hello_c.patch | 11 -
patches/patches/094_c_math.patch | 11 -
patches/patches/095_for3.patch | 20 --
patches/patches/095_interfaces.patch | 11 +
patches/patches/096_hello_c.patch | 11 +
patches/patches/096_memory_allocation.patch | 11 -
patches/patches/097_bit_manipulation.patch | 11 -
patches/patches/097_c_math.patch | 11 +
patches/patches/098_bit_manipulation2.patch | 9 -
patches/patches/098_for3.patch | 20 ++
patches/patches/099_formatting.patch | 11 -
patches/patches/099_memory_allocation.patch | 11 +
patches/patches/100_bit_manipulation.patch | 11 +
patches/patches/100_for4.patch | 11 -
patches/patches/101_bit_manipulation2.patch | 9 +
patches/patches/101_for5.patch | 11 -
patches/patches/102_formatting.patch | 11 +
patches/patches/102_testing.patch | 18 --
patches/patches/103_for4.patch | 11 +
patches/patches/103_tokenization.patch | 11 -
patches/patches/104_for5.patch | 11 +
patches/patches/104_threading.patch | 17 -
patches/patches/105_testing.patch | 18 ++
patches/patches/105_threading2.patch | 13 -
patches/patches/106_files.patch | 20 --
patches/patches/106_tokenization.patch | 11 +
patches/patches/107_files2.patch | 26 --
patches/patches/107_threading.patch | 17 +
patches/patches/108_labeled_switch.patch | 18 --
patches/patches/108_threading2.patch | 13 +
patches/patches/109_files.patch | 20 ++
patches/patches/109_vectors.patch | 13 -
patches/patches/110_files2.patch | 26 ++
patches/patches/110_quiz9.patch | 56 ----
patches/patches/111_labeled_switch.patch | 18 ++
patches/patches/111_packed.patch | 57 ----
patches/patches/112_packed2.patch | 32 --
patches/patches/112_vectors.patch | 13 +
patches/patches/113_quiz9.patch | 56 ++++
patches/patches/114_packed.patch | 57 ++++
patches/patches/115_packed2.patch | 32 ++
88 files changed, 2982 insertions(+), 2932 deletions(-)
delete mode 100644 exercises/092_interfaces.zig
delete mode 100644 exercises/093_hello_c.zig
delete mode 100644 exercises/094_c_math.zig
delete mode 100644 exercises/095_for3.zig
create mode 100644 exercises/095_interfaces.zig
create mode 100644 exercises/096_hello_c.zig
delete mode 100644 exercises/096_memory_allocation.zig
delete mode 100644 exercises/097_bit_manipulation.zig
create mode 100644 exercises/097_c_math.zig
delete mode 100644 exercises/098_bit_manipulation2.zig
create mode 100644 exercises/098_for3.zig
delete mode 100644 exercises/099_formatting.zig
create mode 100644 exercises/099_memory_allocation.zig
create mode 100644 exercises/100_bit_manipulation.zig
delete mode 100644 exercises/100_for4.zig
create mode 100644 exercises/101_bit_manipulation2.zig
delete mode 100644 exercises/101_for5.zig
create mode 100644 exercises/102_formatting.zig
delete mode 100644 exercises/102_testing.zig
create mode 100644 exercises/103_for4.zig
delete mode 100644 exercises/103_tokenization.zig
create mode 100644 exercises/104_for5.zig
delete mode 100644 exercises/104_threading.zig
create mode 100644 exercises/105_testing.zig
delete mode 100644 exercises/105_threading2.zig
delete mode 100644 exercises/106_files.zig
create mode 100644 exercises/106_tokenization.zig
delete mode 100644 exercises/107_files2.zig
create mode 100644 exercises/107_threading.zig
delete mode 100644 exercises/108_labeled_switch.zig
create mode 100644 exercises/108_threading2.zig
create mode 100644 exercises/109_files.zig
delete mode 100644 exercises/109_vectors.zig
create mode 100644 exercises/110_files2.zig
delete mode 100644 exercises/110_quiz9.zig
create mode 100644 exercises/111_labeled_switch.zig
delete mode 100644 exercises/111_packed.zig
delete mode 100644 exercises/112_packed2.zig
create mode 100644 exercises/112_vectors.zig
create mode 100644 exercises/113_quiz9.zig
create mode 100644 exercises/114_packed.zig
create mode 100644 exercises/115_packed2.zig
create mode 100644 patches/patches/091_async8.patch
delete mode 100644 patches/patches/092_interfaces.patch
delete mode 100644 patches/patches/093_hello_c.patch
delete mode 100644 patches/patches/094_c_math.patch
delete mode 100644 patches/patches/095_for3.patch
create mode 100644 patches/patches/095_interfaces.patch
create mode 100644 patches/patches/096_hello_c.patch
delete mode 100644 patches/patches/096_memory_allocation.patch
delete mode 100644 patches/patches/097_bit_manipulation.patch
create mode 100644 patches/patches/097_c_math.patch
delete mode 100644 patches/patches/098_bit_manipulation2.patch
create mode 100644 patches/patches/098_for3.patch
delete mode 100644 patches/patches/099_formatting.patch
create mode 100644 patches/patches/099_memory_allocation.patch
create mode 100644 patches/patches/100_bit_manipulation.patch
delete mode 100644 patches/patches/100_for4.patch
create mode 100644 patches/patches/101_bit_manipulation2.patch
delete mode 100644 patches/patches/101_for5.patch
create mode 100644 patches/patches/102_formatting.patch
delete mode 100644 patches/patches/102_testing.patch
create mode 100644 patches/patches/103_for4.patch
delete mode 100644 patches/patches/103_tokenization.patch
create mode 100644 patches/patches/104_for5.patch
delete mode 100644 patches/patches/104_threading.patch
create mode 100644 patches/patches/105_testing.patch
delete mode 100644 patches/patches/105_threading2.patch
delete mode 100644 patches/patches/106_files.patch
create mode 100644 patches/patches/106_tokenization.patch
delete mode 100644 patches/patches/107_files2.patch
create mode 100644 patches/patches/107_threading.patch
delete mode 100644 patches/patches/108_labeled_switch.patch
create mode 100644 patches/patches/108_threading2.patch
create mode 100644 patches/patches/109_files.patch
delete mode 100644 patches/patches/109_vectors.patch
create mode 100644 patches/patches/110_files2.patch
delete mode 100644 patches/patches/110_quiz9.patch
create mode 100644 patches/patches/111_labeled_switch.patch
delete mode 100644 patches/patches/111_packed.patch
delete mode 100644 patches/patches/112_packed2.patch
create mode 100644 patches/patches/112_vectors.patch
create mode 100644 patches/patches/113_quiz9.patch
create mode 100644 patches/patches/114_packed.patch
create mode 100644 patches/patches/115_packed2.patch
(limited to 'build.zig')
diff --git a/build.zig b/build.zig
index 3dc0520..5e5d3de 100644
--- a/build.zig
+++ b/build.zig
@@ -1153,17 +1153,29 @@ const exercises = [_]Exercise{
},
.{
.main_file = "090_async7.zig",
- .output = "Counter: 400 (expected: 400)",
+ .output = "Counter: 400",
},
.{
.main_file = "091_async8.zig",
- .output = "ABCDEF",
+ .output = "Sum of 1..10 = 55",
+ },
+ .{
+ .main_file = "092_async9.zig",
+ .output = "",
+ .skip = true,
+ },
+ .{
+ .main_file = "093_async10.zig",
+ .output = "",
+ .skip = true,
+ },
+ .{
+ .main_file = "094_async_quiz.zig",
+ .output = "",
.skip = true,
- .skip_hint = "async has not been implemented in the current compiler version.",
},
-
.{
- .main_file = "092_interfaces.zig",
+ .main_file = "095_interfaces.zig",
.output =
\\Daily Insect Report:
\\Ant is alive.
@@ -1172,33 +1184,33 @@ const exercises = [_]Exercise{
, // pay attention to the comma
},
.{
- .main_file = "093_hello_c.zig",
+ .main_file = "096_hello_c.zig",
.output = "Hello C from Zig! - C result is 17 chars written.",
.link_libc = true,
},
.{
- .main_file = "094_c_math.zig",
+ .main_file = "097_c_math.zig",
.output = "The normalized angle of 765.2 degrees is 45.2 degrees.",
.link_libc = true,
},
.{
- .main_file = "095_for3.zig",
+ .main_file = "098_for3.zig",
.output = "1 2 4 7 8 11 13 14 16 17 19\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15",
},
.{
- .main_file = "096_memory_allocation.zig",
+ .main_file = "099_memory_allocation.zig",
.output = "Running Average: 0.30 0.25 0.20 0.18 0.22",
},
.{
- .main_file = "097_bit_manipulation.zig",
+ .main_file = "100_bit_manipulation.zig",
.output = "x = 1011; y = 1101",
},
.{
- .main_file = "098_bit_manipulation2.zig",
+ .main_file = "101_bit_manipulation2.zig",
.output = "Is this a pangram? true!",
},
.{
- .main_file = "099_formatting.zig",
+ .main_file = "102_formatting.zig",
.output =
\\
\\ X | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@@ -1235,11 +1247,11 @@ const exercises = [_]Exercise{
,
},
.{
- .main_file = "100_for4.zig",
+ .main_file = "103_for4.zig",
.output = "Arrays match!",
},
.{
- .main_file = "101_for5.zig",
+ .main_file = "104_for5.zig",
.output =
\\1. Wizard (Gold: 25, XP: 40)
\\2. Bard (Gold: 11, XP: 17)
@@ -1248,12 +1260,12 @@ const exercises = [_]Exercise{
, // pay attention to the comma
},
.{
- .main_file = "102_testing.zig",
+ .main_file = "105_testing.zig",
.output = "",
.kind = .@"test",
},
.{
- .main_file = "103_tokenization.zig",
+ .main_file = "106_tokenization.zig",
.output =
\\My
\\name
@@ -1274,7 +1286,7 @@ const exercises = [_]Exercise{
, // pay attention to the comma
},
.{
- .main_file = "104_threading.zig",
+ .main_file = "107_threading.zig",
.output =
\\Starting work...
\\thread 1: started.
@@ -1288,32 +1300,32 @@ const exercises = [_]Exercise{
, // pay attention to the comma
},
.{
- .main_file = "105_threading2.zig",
+ .main_file = "108_threading2.zig",
.output = "PI ≈ 3.14159265",
},
.{
- .main_file = "106_files.zig",
+ .main_file = "109_files.zig",
.output = "Successfully wrote 18 bytes.",
},
.{
- .main_file = "107_files2.zig",
+ .main_file = "110_files2.zig",
.output =
\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
\\Successfully Read 18 bytes: It's zigling time!
, // pay attention to the comma
},
.{
- .main_file = "108_labeled_switch.zig",
+ .main_file = "111_labeled_switch.zig",
.output = "The pull request has been merged.",
},
.{
- .main_file = "109_vectors.zig",
+ .main_file = "112_vectors.zig",
.output =
\\Max difference (old fn): 0.014
\\Max difference (new fn): 0.014
, // pay attention to the comma
},
- .{ .main_file = "110_quiz9.zig", .output =
+ .{ .main_file = "113_quiz9.zig", .output =
\\Toggle pins with XOR on PORTB
\\-----------------------------
\\ 1100 // (initial state of PORTB)
@@ -1345,11 +1357,11 @@ const exercises = [_]Exercise{
\\= 0110
},
.{
- .main_file = "111_packed.zig",
+ .main_file = "114_packed.zig",
.output = "",
},
.{
- .main_file = "112_packed2.zig",
+ .main_file = "115_packed2.zig",
.output = "",
},
.{
diff --git a/exercises/090_async7.zig b/exercises/090_async7.zig
index f914aef..bfe6ffd 100644
--- a/exercises/090_async7.zig
+++ b/exercises/090_async7.zig
@@ -42,7 +42,7 @@ pub fn main(init: std.process.Init) !void {
try group.await(io);
- print("Counter: {} (expected: 400)\n", .{state.counter});
+ print("Counter: {}\n", .{state.counter});
}
fn increment(io: std.Io, state: *SharedState, times: u32) void {
diff --git a/exercises/091_async8.zig b/exercises/091_async8.zig
index cd9c975..10921c3 100644
--- a/exercises/091_async8.zig
+++ b/exercises/091_async8.zig
@@ -1,35 +1,62 @@
//
-// You have doubtless noticed that 'suspend' requires a block
-// expression like so:
+// Tasks often need to communicate! Io provides Queue for this —
+// a bounded, thread-safe channel for passing data between tasks:
//
-// suspend {}
+// var backing: [16]u32 = undefined;
+// var queue: std.Io.Queue(u32) = .init(&backing);
//
-// The suspend block executes when a function suspends. To get
-// sense for when this happens, please make the following
-// program print the string
+// // Producer task:
+// try queue.putOne(io, value); // blocks if queue is full
//
-// "ABCDEF"
+// // Consumer task:
+// const val = try queue.getOne(io); // blocks if queue is empty
//
-const print = @import("std").debug.print;
+// 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() void {
- print("A", .{});
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
- var frame = async suspendable();
+ var backing: [4]u32 = undefined;
+ var queue: std.Io.Queue(u32) = .init(&backing);
- print("X", .{});
+ var group: std.Io.Group = .init;
- resume frame;
+ group.async(io, producer, .{ io, &queue });
+ group.async(io, consumer, .{ io, &queue });
- print("F", .{});
+ try group.await(io);
}
-fn suspendable() void {
- print("X", .{});
-
- suspend {
- print("X", .{});
+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);
+}
- print("X", .{});
+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/092_interfaces.zig b/exercises/092_interfaces.zig
deleted file mode 100644
index 7775dd5..0000000
--- a/exercises/092_interfaces.zig
+++ /dev/null
@@ -1,127 +0,0 @@
-//
-// Remember our ant and bee simulator constructed with unions
-// back in exercises 55 and 56? There, we demonstrated that
-// unions allow us to treat different data types in a uniform
-// manner.
-//
-// One neat feature was using tagged unions to create a single
-// function to print a status for ants *or* bees by switching:
-//
-// switch (insect) {
-// .still_alive => ... // (print ant stuff)
-// .flowers_visited => ... // (print bee stuff)
-// }
-//
-// Well, that simulation was running just fine until a new insect
-// arrived in the virtual garden, a grasshopper!
-//
-// Doctor Zoraptera started to add grasshopper code to the
-// program, but then she backed away from her keyboard with an
-// angry hissing sound. She had realized that having code for
-// each insect in one place and code to print each insect in
-// another place was going to become unpleasant to maintain when
-// the simulation expanded to hundreds of different insects.
-//
-// Thankfully, Zig has another comptime feature we can use
-// to get out of this dilemma called the 'inline else'.
-//
-// We can replace this redundant code:
-//
-// switch (thing) {
-// .a => |a| special(a),
-// .b => |b| normal(b),
-// .c => |c| normal(c),
-// .d => |d| normal(d),
-// .e => |e| normal(e),
-// ...
-// }
-//
-// With:
-//
-// switch (thing) {
-// .a => |a| special(a),
-// inline else => |t| normal(t),
-// }
-//
-// We can have special handling of some cases and then Zig
-// handles the rest of the matches for us.
-//
-// With this feature, you decide to make an Insect union with a
-// single uniform 'print()' function. All of the insects can
-// then be responsible for printing themselves. And Doctor
-// Zoraptera can calm down and stop gnawing on the furniture.
-//
-const std = @import("std");
-
-const Ant = struct {
- still_alive: bool,
-
- pub fn print(self: Ant) void {
- std.debug.print("Ant is {s}.\n", .{if (self.still_alive) "alive" else "dead"});
- }
-};
-
-const Bee = struct {
- flowers_visited: u16,
-
- pub fn print(self: Bee) void {
- std.debug.print("Bee visited {} flowers.\n", .{self.flowers_visited});
- }
-};
-
-// Here's the new grasshopper. Notice how we've also added print
-// methods to each insect.
-const Grasshopper = struct {
- distance_hopped: u16,
-
- pub fn print(self: Grasshopper) void {
- std.debug.print("Grasshopper hopped {} meters.\n", .{self.distance_hopped});
- }
-};
-
-const Insect = union(enum) {
- ant: Ant,
- bee: Bee,
- grasshopper: Grasshopper,
-
- // Thanks to 'inline else', we can think of this print() as
- // being an interface method. Any member of this union with
- // a print() method can be treated uniformly by outside
- // code without needing to know any other details. Cool!
- pub fn print(self: Insect) void {
- switch (self) {
- inline else => |case| return case.print(),
- }
- }
-};
-
-pub fn main() !void {
- const my_insects = [_]Insect{
- Insect{ .ant = Ant{ .still_alive = true } },
- Insect{ .bee = Bee{ .flowers_visited = 17 } },
- Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } },
- };
-
- std.debug.print("Daily Insect Report:\n", .{});
- for (my_insects) |insect| {
- // Almost done! We want to print() each insect with a
- // single method call here.
- ???
- }
-}
-
-// Our print() method in the Insect union above demonstrates
-// something very similar to the object-oriented concept of an
-// abstract data type. That is, the Insect type doesn't contain
-// the underlying data, and the print() function doesn't
-// actually do the printing.
-//
-// The point of an interface is to support generic programming:
-// the ability to treat different things as if they were the
-// same to cut down on clutter and conceptual complexity.
-//
-// The Daily Insect Report doesn't need to worry about *which*
-// insects are in the report - they all print the same way via
-// the interface!
-//
-// Doctor Zoraptera loves it.
diff --git a/exercises/093_hello_c.zig b/exercises/093_hello_c.zig
deleted file mode 100644
index 182e0b0..0000000
--- a/exercises/093_hello_c.zig
+++ /dev/null
@@ -1,68 +0,0 @@
-//
-// When Andrew Kelley announced the idea of a new programming language
-// - namely Zig - in his blog on February 8, 2016, he also immediately
-// stated his ambitious goal: to replace the C language!
-//
-// In order to be able to achieve this goal at all, Zig should be
-// as compatible as possible with its "predecessor".
-// Only if it is possible to exchange individual modules in existing
-// C programs without having to use complicated wrappers,
-// the undertaking has a chance of success.
-//
-// So it is not surprising that calling C functions and vice versa
-// is extremely "smooth".
-//
-// To call C functions in Zig, you only need to specify the library
-// that contains said function. For this purpose there is a built-in
-// function corresponding to the well-known @import():
-//
-// @cImport()
-//
-// All required libraries can now be included in the usual Zig notation:
-//
-// const c = @cImport({
-// @cInclude("stdio.h");
-// @cInclude("...");
-// });
-//
-// Now a function can be called via the (in this example) constant 'c':
-//
-// c.puts("Hello world!");
-//
-// By the way, most C functions have return values in the form of an
-// integer value. Errors can then be evaluated (return < 0) or other
-// information can be obtained. For example, 'puts' returns the number
-// of characters output.
-//
-// So that all this does not remain a dry theory now, let's just start
-// and call a C function out of Zig.
-
-// our well-known "import" for Zig
-const std = @import("std");
-
-// and here the new import for C
-const c = @cImport({
- @cInclude("unistd.h");
-});
-
-pub fn main() void {
-
- // In order to output text that can be evaluated by the
- // Zig Builder, we need to write it to the Error output.
- // In Zig, we do this with "std.debug.print" and in C we can
- // specify a file descriptor i.e. 2 for error console.
- //
- // In this exercise we use 'write' to output 17 chars,
- // but something is still missing...
- const c_res = write(2, "Hello C from Zig!", 17);
-
- // let's see what the result from C is:
- std.debug.print(" - C result is {d} chars written.\n", .{c_res});
-}
-//
-// Something must be considered when compiling with C functions.
-// Namely that the Zig compiler knows that it should include
-// corresponding libraries. For this purpose we call the compiler
-// with the parameter "lc" for such a program,
-// e.g. "zig run -lc hello_c.zig".
-//
diff --git a/exercises/094_c_math.zig b/exercises/094_c_math.zig
deleted file mode 100644
index ec59a86..0000000
--- a/exercises/094_c_math.zig
+++ /dev/null
@@ -1,41 +0,0 @@
-//
-// Often, C functions are used where no equivalent Zig function exists
-// yet. Okay, that's getting less and less. ;-)
-//
-// Since the integration of a C function is very simple, as already
-// seen in the last exercise, it naturally offers itself to use the
-// very large variety of C functions for our own programs.
-// As an example:
-//
-// Let's say we have a given angle of 765.2 degrees. If we want to
-// normalize that, it means that we have to subtract X * 360 degrees
-// to get the correct angle.
-// How could we do that? A good method is to use the modulo function.
-// But if we write "765.2 % 360", it only works with float values
-// that are known at compile time.
-// In Zig, we would use @mod(a, b) instead.
-//
-// Let us now assume that we cannot do this in Zig, but only with
-// a C function from the standard library. In the library "math",
-// there is a function called "fmod"; the "f" stands for floating
-// and means that we can solve modulo for real numbers. With this
-// function, it should be possible to normalize our angle.
-// Let's go.
-
-const std = @import("std");
-
-const c = @cImport({
- // What do we need here?
- ???
-});
-
-pub fn main() !void {
- const angle = 765.2;
- const circle = 360;
-
- // Here we call the C function 'fmod' to get our normalized angle.
- const result = c.fmod(angle, circle);
-
- // We use formatters for the desired precision and to truncate the decimal places
- std.debug.print("The normalized angle of {d: >3.1} degrees is {d: >3.1} degrees.\n", .{ angle, result });
-}
diff --git a/exercises/095_for3.zig b/exercises/095_for3.zig
deleted file mode 100644
index 77a1b56..0000000
--- a/exercises/095_for3.zig
+++ /dev/null
@@ -1,81 +0,0 @@
-//
-// The Zig language is in rapid development and continuously
-// improves the language constructs. Ziglings evolves with it.
-//
-// Until version 0.11, Zig's 'for' loops did not directly
-// replicate the functionality of the C-style: "for(a;b;c)"
-// which are so well suited for iterating over a numeric
-// sequence.
-//
-// Instead, 'while' loops with counters clumsily stood in their
-// place:
-//
-// var i: usize = 0;
-// while (i < 10) : (i += 1) {
-// // Here variable 'i' will have each value 0 to 9.
-// }
-//
-// But here we are in the glorious future and Zig's 'for' loops
-// can now take this form:
-//
-// for (0..10) |i| {
-// // Here variable 'i' will have each value 0 to 9.
-// }
-//
-// The key to understanding this example is to know that '0..9'
-// uses the new range syntax:
-//
-// 0..10 is a range from 0 to 9
-// 1..4 is a range from 1 to 3
-//
-// Crucially, the end value is EXCLUSIVE.
-//
-// At the moment, ranges in loops are only supported in 'for' loops.
-//
-// Perhaps you recall Exercise 13? We were printing a numeric
-// sequence like so:
-//
-// var n: u32 = 1;
-//
-// // I want to print every number between 1 and 20 that is NOT
-// // divisible by 3 or 5.
-// while (n <= 20) : (n += 1) {
-// // The '%' symbol is the "modulo" operator and it
-// // returns the remainder after division.
-// if (n % 3 == 0) continue;
-// if (n % 5 == 0) continue;
-// std.debug.print("{} ", .{n});
-// }
-//
-// Let's try out the new form of 'for' to re-implement that
-// exercise:
-//
-const std = @import("std");
-
-pub fn main() void {
-
- // I want to print every number between 1 and 20 that is NOT
- // divisible by 3 or 5.
- for (???) |n| {
-
- // The '%' symbol is the "modulo" operator and it
- // returns the remainder after division.
- if (n % 3 == 0) continue;
- if (n % 5 == 0) continue;
- std.debug.print("{} ", .{n});
- }
-
- std.debug.print("\n", .{});
-
- // Let's also print every number from 1 through 15
- for (???) |n| {
- std.debug.print("{} ", .{n});
- }
- std.debug.print("\n", .{});
-}
-//
-// That's a bit nicer, right?
-//
-// Of course, both 'while' and 'for' have different advantages.
-// Exercises 11, 12, and 14 would NOT be simplified by switching
-// a 'while' for a 'for'.
diff --git a/exercises/095_interfaces.zig b/exercises/095_interfaces.zig
new file mode 100644
index 0000000..7775dd5
--- /dev/null
+++ b/exercises/095_interfaces.zig
@@ -0,0 +1,127 @@
+//
+// Remember our ant and bee simulator constructed with unions
+// back in exercises 55 and 56? There, we demonstrated that
+// unions allow us to treat different data types in a uniform
+// manner.
+//
+// One neat feature was using tagged unions to create a single
+// function to print a status for ants *or* bees by switching:
+//
+// switch (insect) {
+// .still_alive => ... // (print ant stuff)
+// .flowers_visited => ... // (print bee stuff)
+// }
+//
+// Well, that simulation was running just fine until a new insect
+// arrived in the virtual garden, a grasshopper!
+//
+// Doctor Zoraptera started to add grasshopper code to the
+// program, but then she backed away from her keyboard with an
+// angry hissing sound. She had realized that having code for
+// each insect in one place and code to print each insect in
+// another place was going to become unpleasant to maintain when
+// the simulation expanded to hundreds of different insects.
+//
+// Thankfully, Zig has another comptime feature we can use
+// to get out of this dilemma called the 'inline else'.
+//
+// We can replace this redundant code:
+//
+// switch (thing) {
+// .a => |a| special(a),
+// .b => |b| normal(b),
+// .c => |c| normal(c),
+// .d => |d| normal(d),
+// .e => |e| normal(e),
+// ...
+// }
+//
+// With:
+//
+// switch (thing) {
+// .a => |a| special(a),
+// inline else => |t| normal(t),
+// }
+//
+// We can have special handling of some cases and then Zig
+// handles the rest of the matches for us.
+//
+// With this feature, you decide to make an Insect union with a
+// single uniform 'print()' function. All of the insects can
+// then be responsible for printing themselves. And Doctor
+// Zoraptera can calm down and stop gnawing on the furniture.
+//
+const std = @import("std");
+
+const Ant = struct {
+ still_alive: bool,
+
+ pub fn print(self: Ant) void {
+ std.debug.print("Ant is {s}.\n", .{if (self.still_alive) "alive" else "dead"});
+ }
+};
+
+const Bee = struct {
+ flowers_visited: u16,
+
+ pub fn print(self: Bee) void {
+ std.debug.print("Bee visited {} flowers.\n", .{self.flowers_visited});
+ }
+};
+
+// Here's the new grasshopper. Notice how we've also added print
+// methods to each insect.
+const Grasshopper = struct {
+ distance_hopped: u16,
+
+ pub fn print(self: Grasshopper) void {
+ std.debug.print("Grasshopper hopped {} meters.\n", .{self.distance_hopped});
+ }
+};
+
+const Insect = union(enum) {
+ ant: Ant,
+ bee: Bee,
+ grasshopper: Grasshopper,
+
+ // Thanks to 'inline else', we can think of this print() as
+ // being an interface method. Any member of this union with
+ // a print() method can be treated uniformly by outside
+ // code without needing to know any other details. Cool!
+ pub fn print(self: Insect) void {
+ switch (self) {
+ inline else => |case| return case.print(),
+ }
+ }
+};
+
+pub fn main() !void {
+ const my_insects = [_]Insect{
+ Insect{ .ant = Ant{ .still_alive = true } },
+ Insect{ .bee = Bee{ .flowers_visited = 17 } },
+ Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } },
+ };
+
+ std.debug.print("Daily Insect Report:\n", .{});
+ for (my_insects) |insect| {
+ // Almost done! We want to print() each insect with a
+ // single method call here.
+ ???
+ }
+}
+
+// Our print() method in the Insect union above demonstrates
+// something very similar to the object-oriented concept of an
+// abstract data type. That is, the Insect type doesn't contain
+// the underlying data, and the print() function doesn't
+// actually do the printing.
+//
+// The point of an interface is to support generic programming:
+// the ability to treat different things as if they were the
+// same to cut down on clutter and conceptual complexity.
+//
+// The Daily Insect Report doesn't need to worry about *which*
+// insects are in the report - they all print the same way via
+// the interface!
+//
+// Doctor Zoraptera loves it.
diff --git a/exercises/096_hello_c.zig b/exercises/096_hello_c.zig
new file mode 100644
index 0000000..182e0b0
--- /dev/null
+++ b/exercises/096_hello_c.zig
@@ -0,0 +1,68 @@
+//
+// When Andrew Kelley announced the idea of a new programming language
+// - namely Zig - in his blog on February 8, 2016, he also immediately
+// stated his ambitious goal: to replace the C language!
+//
+// In order to be able to achieve this goal at all, Zig should be
+// as compatible as possible with its "predecessor".
+// Only if it is possible to exchange individual modules in existing
+// C programs without having to use complicated wrappers,
+// the undertaking has a chance of success.
+//
+// So it is not surprising that calling C functions and vice versa
+// is extremely "smooth".
+//
+// To call C functions in Zig, you only need to specify the library
+// that contains said function. For this purpose there is a built-in
+// function corresponding to the well-known @import():
+//
+// @cImport()
+//
+// All required libraries can now be included in the usual Zig notation:
+//
+// const c = @cImport({
+// @cInclude("stdio.h");
+// @cInclude("...");
+// });
+//
+// Now a function can be called via the (in this example) constant 'c':
+//
+// c.puts("Hello world!");
+//
+// By the way, most C functions have return values in the form of an
+// integer value. Errors can then be evaluated (return < 0) or other
+// information can be obtained. For example, 'puts' returns the number
+// of characters output.
+//
+// So that all this does not remain a dry theory now, let's just start
+// and call a C function out of Zig.
+
+// our well-known "import" for Zig
+const std = @import("std");
+
+// and here the new import for C
+const c = @cImport({
+ @cInclude("unistd.h");
+});
+
+pub fn main() void {
+
+ // In order to output text that can be evaluated by the
+ // Zig Builder, we need to write it to the Error output.
+ // In Zig, we do this with "std.debug.print" and in C we can
+ // specify a file descriptor i.e. 2 for error console.
+ //
+ // In this exercise we use 'write' to output 17 chars,
+ // but something is still missing...
+ const c_res = write(2, "Hello C from Zig!", 17);
+
+ // let's see what the result from C is:
+ std.debug.print(" - C result is {d} chars written.\n", .{c_res});
+}
+//
+// Something must be considered when compiling with C functions.
+// Namely that the Zig compiler knows that it should include
+// corresponding libraries. For this purpose we call the compiler
+// with the parameter "lc" for such a program,
+// e.g. "zig run -lc hello_c.zig".
+//
diff --git a/exercises/096_memory_allocation.zig b/exercises/096_memory_allocation.zig
deleted file mode 100644
index 58de7b0..0000000
--- a/exercises/096_memory_allocation.zig
+++ /dev/null
@@ -1,78 +0,0 @@
-//
-// In most of the examples so far, the inputs are known at compile
-// time, thus the amount of memory used by the program is fixed.
-// However, if responding to input whose size is not known at compile
-// time, such as:
-// - user input via command-line arguments
-// - inputs from another program
-//
-// You'll need to request memory for your program to be allocated by
-// your operating system at runtime.
-//
-// Zig provides several different allocators. In the Zig
-// documentation, it recommends the Arena allocator for simple
-// programs which allocate once and then exit:
-//
-// const std = @import("std");
-//
-// // memory allocation can fail, so the return type is !void
-// pub fn main() !void {
-//
-// var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
-// defer arena.deinit();
-//
-// const allocator = arena.allocator();
-//
-// const ptr = try allocator.create(i32);
-// std.debug.print("ptr={*}\n", .{ptr});
-//
-// const slice_ptr = try allocator.alloc(f64, 5);
-// std.debug.print("slice_ptr={*}\n", .{slice_ptr});
-// }
-
-// Instead of a simple integer or a slice with a constant size,
-// this program requires allocating a slice that is the same size
-// as an input array.
-
-// Given a series of numbers, take the running average. In other
-// words, each item N should contain the average of the last N
-// elements.
-
-const std = @import("std");
-
-fn runningAverage(arr: []const f64, avg: []f64) void {
- var sum: f64 = 0;
-
- for (0.., arr) |index, val| {
- sum += val;
- const f_index: f64 = @floatFromInt(index + 1);
- avg[index] = sum / f_index;
- }
-}
-
-pub fn main() !void {
- // pretend this was defined by reading in user input
- const arr: []const f64 = &[_]f64{ 0.3, 0.2, 0.1, 0.1, 0.4 };
-
- // initialize the allocator
- var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
-
- // free the memory on exit
- defer arena.deinit();
-
- // initialize the allocator
- const allocator = arena.allocator();
-
- // allocate memory for this array
- const avg: []f64 = ???;
-
- runningAverage(arr, avg);
- std.debug.print("Running Average: ", .{});
- for (avg) |val| {
- std.debug.print("{d:.2} ", .{val});
- }
- std.debug.print("\n", .{});
-}
-
-// For more details on memory allocation and the different types of
-// memory allocators, see https://www.youtube.com/watch?v=vHWiDx_l4V0
diff --git a/exercises/097_bit_manipulation.zig b/exercises/097_bit_manipulation.zig
deleted file mode 100644
index 0e64ad7..0000000
--- a/exercises/097_bit_manipulation.zig
+++ /dev/null
@@ -1,95 +0,0 @@
-//
-// Bit manipulation is a very powerful tool, also from Zig.
-// Since the dawn of the computer age, numerous algorithms have been
-// developed that solve tasks solely by moving, setting, or logically
-// combining bits.
-//
-// Zig also uses direct bit manipulation in its standard library for
-// functions where possible. And it is often possible with calculations
-// based on integers.
-//
-// At first glance, it is often not easy to understand what exactly these
-// algorithms do when only "numbers" in memory areas change outwardly.
-// However, it should never be forgotten that the numbers only represent
-// the interpretation of the bit sequences.
-//
-// Quasi the reversed case we have otherwise, namely that we represent
-// numbers in bit sequences.
-//
-// We remember: 1 byte = 8 bits = 11111111 = 255 decimal = FF hex.
-//
-// Zig provides all the necessary functions to change the bits inside
-// a variable. It is distinguished whether the bit change leads to an
-// overflow or not. The details are in the Zig documentation in section
-// "Table of Operators".
-//
-// Here are some examples of how the bits of variables can be changed:
-//
-// const numOne: u8 = 15; // = 0000 1111
-// const numTwo: u8 = 245; // = 1111 0101
-//
-// const res1 = numOne >> 4; // = 0000 0000 - shift right
-// const res2 = numOne << 4; // = 1111 0000 - shift left
-// const res3 = numOne & numTwo; // = 0000 0101 - and
-// const res4 = numOne | numTwo; // = 1111 1111 - or
-// const res5 = numOne ^ numTwo; // = 1111 1010 - xor
-//
-//
-// To familiarize ourselves with bit manipulation, we start with a simple
-// but often underestimated function and then add other exercises in
-// loose order.
-//
-// The following text contains excerpts from Wikipedia.
-//
-// Swap
-// In computer programming, the act of swapping two variables refers to
-// mutually exchanging the values of the variables. Usually, this is
-// done with the data in memory. For example, in a program, two variables
-// may be defined thus (in pseudocode):
-//
-// data_item x := 1
-// data_item y := 0
-//
-// swap (x, y);
-//
-// After swap() is performed, x will contain the value 0 and y will
-// contain 1; their values have been exchanged. The simplest and probably
-// most widely used method to swap two variables is to use a third temporary
-// variable:
-//
-// define swap (x, y)
-// temp := x
-// x := y
-// y := temp
-//
-// However, with integers we can also achieve the swap function simply by
-// bit manipulation. To do this, the variables are mutually linked with xor
-// and the result is the same.
-
-const std = @import("std");
-const print = std.debug.print;
-
-pub fn main() !void {
-
- // Let us use 1101 and 1011 as values for x and y
- var x: u8 = 0b1101;
- var y: u8 = 0b1011;
-
- // Now we swap the values of the two variables by doing xor on them
- x ^= y;
- y ^= x;
-
- // What must be written here?
- ???;
-
- print("x = {b}; y = {b}\n", .{ x, y });
-}
-
-// This variable swap takes advantage of the fact that the value resulting
-// from the xor of two values contains both of these values.
-// This circumstance was (and still is) sometimes used for encryption.
-// Value XOR Key = Crypto. => Crypto XOR Key = Value.
-// Since this can be swapped arbitrarily, you can swap two variables in this way.
-//
-// For Crypto it is better not to use this, but in sorting algorithms like
-// Bubble Sort it works very well.
diff --git a/exercises/097_c_math.zig b/exercises/097_c_math.zig
new file mode 100644
index 0000000..ec59a86
--- /dev/null
+++ b/exercises/097_c_math.zig
@@ -0,0 +1,41 @@
+//
+// Often, C functions are used where no equivalent Zig function exists
+// yet. Okay, that's getting less and less. ;-)
+//
+// Since the integration of a C function is very simple, as already
+// seen in the last exercise, it naturally offers itself to use the
+// very large variety of C functions for our own programs.
+// As an example:
+//
+// Let's say we have a given angle of 765.2 degrees. If we want to
+// normalize that, it means that we have to subtract X * 360 degrees
+// to get the correct angle.
+// How could we do that? A good method is to use the modulo function.
+// But if we write "765.2 % 360", it only works with float values
+// that are known at compile time.
+// In Zig, we would use @mod(a, b) instead.
+//
+// Let us now assume that we cannot do this in Zig, but only with
+// a C function from the standard library. In the library "math",
+// there is a function called "fmod"; the "f" stands for floating
+// and means that we can solve modulo for real numbers. With this
+// function, it should be possible to normalize our angle.
+// Let's go.
+
+const std = @import("std");
+
+const c = @cImport({
+ // What do we need here?
+ ???
+});
+
+pub fn main() !void {
+ const angle = 765.2;
+ const circle = 360;
+
+ // Here we call the C function 'fmod' to get our normalized angle.
+ const result = c.fmod(angle, circle);
+
+ // We use formatters for the desired precision and to truncate the decimal places
+ std.debug.print("The normalized angle of {d: >3.1} degrees is {d: >3.1} degrees.\n", .{ angle, result });
+}
diff --git a/exercises/098_bit_manipulation2.zig b/exercises/098_bit_manipulation2.zig
deleted file mode 100644
index 8b51265..0000000
--- a/exercises/098_bit_manipulation2.zig
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-// Another useful application for bit manipulation is setting bits as flags.
-// This is especially useful when processing lists of something and storing
-// the states of the entries, e.g. a list of numbers and for each prime
-// number a flag is set.
-//
-// As an example, let's take the Pangram exercise from Exercism:
-// https://exercism.org/tracks/zig/exercises/pangram
-//
-// A pangram is a sentence using every letter of the alphabet at least once.
-// It is case insensitive, so it doesn't matter if a letter is lower-case
-// or upper-case. The best known English pangram is:
-//
-// "The quick brown fox jumps over the lazy dog."
-//
-// There are several ways to select the letters that appear in the pangram
-// (and it doesn't matter if they appear once or several times).
-//
-// For example, you could take an array of bool and set the value to 'true'
-// for each letter in the order of the alphabet (a=0; b=1; etc.) found in
-// the sentence. However, this is neither memory efficient nor particularly
-// fast. Instead we choose a simpler approach that is very similar in principle:
-// We define a variable with at least 26 bits (e.g. u32) and set the bit for
-// each letter that is found in the corresponding position.
-//
-// Zig provides functions for this in the standard library, but we prefer to
-// solve it without these extras, after all we want to learn something.
-//
-const std = @import("std");
-const ascii = std.ascii;
-const print = std.debug.print;
-
-pub fn main() !void {
- // let's check the pangram
- print("Is this a pangram? {}!\n", .{isPangram("The quick brown fox jumps over the lazy dog.")});
-}
-
-fn isPangram(str: []const u8) bool {
- // first we check if the string has at least 26 characters
- if (str.len < 26) return false;
-
- // we use a 32 bit variable of which we need 26 bits
- var bits: u32 = 0;
-
- // loop about all characters in the string
- for (str) |c| {
- // if the character is an alphabetical character
- if (ascii.isAscii(c) and ascii.isAlphabetic(c)) {
- // then we set the bit at the position
- //
- // to do this, we use a little trick:
- // since the letters in the ASCII table start at 65
- // and are numbered sequentially, we simply subtract the
- // first letter (in this case the 'a') from the character
- // found, and thus get the position of the desired bit
- bits |= @as(u32, 1) << @truncate(ascii.toLower(c) - 'a');
- }
- }
- // last we return the comparison if all 26 bits are set,
- // and if so, we know the given string is a pangram
- //
- // but what do we have to compare?
- return bits == 0x..???;
-}
diff --git a/exercises/098_for3.zig b/exercises/098_for3.zig
new file mode 100644
index 0000000..77a1b56
--- /dev/null
+++ b/exercises/098_for3.zig
@@ -0,0 +1,81 @@
+//
+// The Zig language is in rapid development and continuously
+// improves the language constructs. Ziglings evolves with it.
+//
+// Until version 0.11, Zig's 'for' loops did not directly
+// replicate the functionality of the C-style: "for(a;b;c)"
+// which are so well suited for iterating over a numeric
+// sequence.
+//
+// Instead, 'while' loops with counters clumsily stood in their
+// place:
+//
+// var i: usize = 0;
+// while (i < 10) : (i += 1) {
+// // Here variable 'i' will have each value 0 to 9.
+// }
+//
+// But here we are in the glorious future and Zig's 'for' loops
+// can now take this form:
+//
+// for (0..10) |i| {
+// // Here variable 'i' will have each value 0 to 9.
+// }
+//
+// The key to understanding this example is to know that '0..9'
+// uses the new range syntax:
+//
+// 0..10 is a range from 0 to 9
+// 1..4 is a range from 1 to 3
+//
+// Crucially, the end value is EXCLUSIVE.
+//
+// At the moment, ranges in loops are only supported in 'for' loops.
+//
+// Perhaps you recall Exercise 13? We were printing a numeric
+// sequence like so:
+//
+// var n: u32 = 1;
+//
+// // I want to print every number between 1 and 20 that is NOT
+// // divisible by 3 or 5.
+// while (n <= 20) : (n += 1) {
+// // The '%' symbol is the "modulo" operator and it
+// // returns the remainder after division.
+// if (n % 3 == 0) continue;
+// if (n % 5 == 0) continue;
+// std.debug.print("{} ", .{n});
+// }
+//
+// Let's try out the new form of 'for' to re-implement that
+// exercise:
+//
+const std = @import("std");
+
+pub fn main() void {
+
+ // I want to print every number between 1 and 20 that is NOT
+ // divisible by 3 or 5.
+ for (???) |n| {
+
+ // The '%' symbol is the "modulo" operator and it
+ // returns the remainder after division.
+ if (n % 3 == 0) continue;
+ if (n % 5 == 0) continue;
+ std.debug.print("{} ", .{n});
+ }
+
+ std.debug.print("\n", .{});
+
+ // Let's also print every number from 1 through 15
+ for (???) |n| {
+ std.debug.print("{} ", .{n});
+ }
+ std.debug.print("\n", .{});
+}
+//
+// That's a bit nicer, right?
+//
+// Of course, both 'while' and 'for' have different advantages.
+// Exercises 11, 12, and 14 would NOT be simplified by switching
+// a 'while' for a 'for'.
diff --git a/exercises/099_formatting.zig b/exercises/099_formatting.zig
deleted file mode 100644
index be16978..0000000
--- a/exercises/099_formatting.zig
+++ /dev/null
@@ -1,140 +0,0 @@
-//
-// Terminals have come a long way over the years. Starting with
-// monochrome lines on flickering CRT monitors and continuously
-// improving to today's modern terminal emulators with sharp
-// images, true color, fonts, ligatures, and characters in every
-// known language.
-//
-// Formatting our results to be appealing and allow quick visual
-// comprehension of the information is what users desire. <3
-//
-// C set string formatting standards over the years, and Zig is
-// following suit and growing daily. Due to this growth, there is
-// no official documentation for standard library features such
-// as string formatting.
-//
-// Therefore, the comments for the format() function are the only
-// way to definitively learn how to format strings in Zig:
-//
-// https://codeberg.org/ziglang/zig/src/branch/master/lib/std/Io/Writer.zig#L537
-//
-// Zig already has a very nice selection of formatting options.
-// These can be used in different ways, but generally to convert
-// numerical values into various text representations. The results
-// can be used for direct output to a terminal or stored for
-// later use or written to a file. The latter is useful when
-// large amounts of data are to be processed by other programs.
-//
-// In Ziglings, we are concerned with the output to the console.
-// But since the formatting instructions for files are the same,
-// what you learn applies universally.
-//
-// Since we write to "debug" output in Ziglings, our answers
-// usually look something like this:
-//
-// print("Text {placeholder} another text \n", .{foo});
-//
-// In addition to being replaced with foo in this example, the
-// {placeholder} in the string can also have formatting applied.
-// How does that work?
-//
-// This actually happens in several stages. In one stage, escape
-// sequences are evaluated. The one we've seen the most
-// (including the example above) is "\n" which means "line feed".
-// Whenever this statement is found, a new line is started in the
-// output. Escape sequences can also be written one after the
-// other, e.g. "\n\n" will cause two line feeds.
-//
-// By the way, the result of these escape sequences is passed
-// directly to the terminal program. Other than translating them
-// into control codes, escape sequences have nothing to do with
-// Zig. Zig knows nothing about "line feeds" or "tabs" or
-// "bells".
-//
-// The formatting that Zig *does* perform itself is found in the
-// curly brackets: "{placeholder}". Formatting instructions in
-// the placeholder will determine how the corresponding value,
-// e.g. foo, is displayed.
-//
-// And this is where it gets exciting, because format() accepts a
-// variety of formatting instructions. It's basically a tiny
-// language of its own. Here's a numeric example:
-//
-// print("Catch-0x{x:0>4}.", .{twenty_two});
-//
-// This formatting instruction outputs a hexadecimal number with
-// leading zeros:
-//
-// Catch-0x0016.
-//
-// Or you can center-align a string like so:
-//
-// print("{s:*^20}\n", .{"Hello!"});
-//
-// Output:
-//
-// *******Hello!*******
-//
-// Let's try making use of some formatting. We've decided that
-// the one thing missing from our lives is a multiplication table
-// for all numbers from 1-15. We want the table to be nice and
-// neat, with numbers in straight columns like so:
-//
-// X | 1 2 3 4 5 ...
-// ---+---+---+---+---+---+
-// 1 | 1 2 3 4 5
-//
-// 2 | 2 4 6 8 10
-//
-// 3 | 3 6 9 12 15
-//
-// 4 | 4 8 12 16 20
-//
-// 5 | 5 10 15 20 25
-//
-// ...
-//
-// Without string formatting, this would be a more challenging
-// assignment because the number of digits in the numbers varies
-// from 1 to 3. But formatting can help us with that.
-//
-const std = @import("std");
-const print = std.debug.print;
-
-pub fn main() !void {
- // Max number to multiply
- const size = 15;
-
- // Print the header:
- //
- // We start with a single 'X' for the diagonal.
- print("\n X |", .{});
-
- // Header row with all numbers from 1 to size.
- for (0..size) |n| {
- print("{d:>3} ", .{n + 1});
- }
- print("\n", .{});
-
- // Header column rule line.
- var n: u8 = 0;
- while (n <= size) : (n += 1) {
- print("---+", .{});
- }
- print("\n", .{});
-
- // Now the actual table. (Is there anything more beautiful
- // than a well-formatted table?)
- for (0..size) |a| {
- print("{d:>2} |", .{a + 1});
-
- for (0..size) |b| {
- // What formatting is needed here to make our columns
- // nice and straight?
- print("{???} ", .{(a + 1) * (b + 1)});
- }
-
- // After each row we use double line feed:
- print("\n\n", .{});
- }
-}
diff --git a/exercises/099_memory_allocation.zig b/exercises/099_memory_allocation.zig
new file mode 100644
index 0000000..58de7b0
--- /dev/null
+++ b/exercises/099_memory_allocation.zig
@@ -0,0 +1,78 @@
+//
+// In most of the examples so far, the inputs are known at compile
+// time, thus the amount of memory used by the program is fixed.
+// However, if responding to input whose size is not known at compile
+// time, such as:
+// - user input via command-line arguments
+// - inputs from another program
+//
+// You'll need to request memory for your program to be allocated by
+// your operating system at runtime.
+//
+// Zig provides several different allocators. In the Zig
+// documentation, it recommends the Arena allocator for simple
+// programs which allocate once and then exit:
+//
+// const std = @import("std");
+//
+// // memory allocation can fail, so the return type is !void
+// pub fn main() !void {
+//
+// var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+// defer arena.deinit();
+//
+// const allocator = arena.allocator();
+//
+// const ptr = try allocator.create(i32);
+// std.debug.print("ptr={*}\n", .{ptr});
+//
+// const slice_ptr = try allocator.alloc(f64, 5);
+// std.debug.print("slice_ptr={*}\n", .{slice_ptr});
+// }
+
+// Instead of a simple integer or a slice with a constant size,
+// this program requires allocating a slice that is the same size
+// as an input array.
+
+// Given a series of numbers, take the running average. In other
+// words, each item N should contain the average of the last N
+// elements.
+
+const std = @import("std");
+
+fn runningAverage(arr: []const f64, avg: []f64) void {
+ var sum: f64 = 0;
+
+ for (0.., arr) |index, val| {
+ sum += val;
+ const f_index: f64 = @floatFromInt(index + 1);
+ avg[index] = sum / f_index;
+ }
+}
+
+pub fn main() !void {
+ // pretend this was defined by reading in user input
+ const arr: []const f64 = &[_]f64{ 0.3, 0.2, 0.1, 0.1, 0.4 };
+
+ // initialize the allocator
+ var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+
+ // free the memory on exit
+ defer arena.deinit();
+
+ // initialize the allocator
+ const allocator = arena.allocator();
+
+ // allocate memory for this array
+ const avg: []f64 = ???;
+
+ runningAverage(arr, avg);
+ std.debug.print("Running Average: ", .{});
+ for (avg) |val| {
+ std.debug.print("{d:.2} ", .{val});
+ }
+ std.debug.print("\n", .{});
+}
+
+// For more details on memory allocation and the different types of
+// memory allocators, see https://www.youtube.com/watch?v=vHWiDx_l4V0
diff --git a/exercises/100_bit_manipulation.zig b/exercises/100_bit_manipulation.zig
new file mode 100644
index 0000000..0e64ad7
--- /dev/null
+++ b/exercises/100_bit_manipulation.zig
@@ -0,0 +1,95 @@
+//
+// Bit manipulation is a very powerful tool, also from Zig.
+// Since the dawn of the computer age, numerous algorithms have been
+// developed that solve tasks solely by moving, setting, or logically
+// combining bits.
+//
+// Zig also uses direct bit manipulation in its standard library for
+// functions where possible. And it is often possible with calculations
+// based on integers.
+//
+// At first glance, it is often not easy to understand what exactly these
+// algorithms do when only "numbers" in memory areas change outwardly.
+// However, it should never be forgotten that the numbers only represent
+// the interpretation of the bit sequences.
+//
+// Quasi the reversed case we have otherwise, namely that we represent
+// numbers in bit sequences.
+//
+// We remember: 1 byte = 8 bits = 11111111 = 255 decimal = FF hex.
+//
+// Zig provides all the necessary functions to change the bits inside
+// a variable. It is distinguished whether the bit change leads to an
+// overflow or not. The details are in the Zig documentation in section
+// "Table of Operators".
+//
+// Here are some examples of how the bits of variables can be changed:
+//
+// const numOne: u8 = 15; // = 0000 1111
+// const numTwo: u8 = 245; // = 1111 0101
+//
+// const res1 = numOne >> 4; // = 0000 0000 - shift right
+// const res2 = numOne << 4; // = 1111 0000 - shift left
+// const res3 = numOne & numTwo; // = 0000 0101 - and
+// const res4 = numOne | numTwo; // = 1111 1111 - or
+// const res5 = numOne ^ numTwo; // = 1111 1010 - xor
+//
+//
+// To familiarize ourselves with bit manipulation, we start with a simple
+// but often underestimated function and then add other exercises in
+// loose order.
+//
+// The following text contains excerpts from Wikipedia.
+//
+// Swap
+// In computer programming, the act of swapping two variables refers to
+// mutually exchanging the values of the variables. Usually, this is
+// done with the data in memory. For example, in a program, two variables
+// may be defined thus (in pseudocode):
+//
+// data_item x := 1
+// data_item y := 0
+//
+// swap (x, y);
+//
+// After swap() is performed, x will contain the value 0 and y will
+// contain 1; their values have been exchanged. The simplest and probably
+// most widely used method to swap two variables is to use a third temporary
+// variable:
+//
+// define swap (x, y)
+// temp := x
+// x := y
+// y := temp
+//
+// However, with integers we can also achieve the swap function simply by
+// bit manipulation. To do this, the variables are mutually linked with xor
+// and the result is the same.
+
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn main() !void {
+
+ // Let us use 1101 and 1011 as values for x and y
+ var x: u8 = 0b1101;
+ var y: u8 = 0b1011;
+
+ // Now we swap the values of the two variables by doing xor on them
+ x ^= y;
+ y ^= x;
+
+ // What must be written here?
+ ???;
+
+ print("x = {b}; y = {b}\n", .{ x, y });
+}
+
+// This variable swap takes advantage of the fact that the value resulting
+// from the xor of two values contains both of these values.
+// This circumstance was (and still is) sometimes used for encryption.
+// Value XOR Key = Crypto. => Crypto XOR Key = Value.
+// Since this can be swapped arbitrarily, you can swap two variables in this way.
+//
+// For Crypto it is better not to use this, but in sorting algorithms like
+// Bubble Sort it works very well.
diff --git a/exercises/100_for4.zig b/exercises/100_for4.zig
deleted file mode 100644
index c8a1161..0000000
--- a/exercises/100_for4.zig
+++ /dev/null
@@ -1,62 +0,0 @@
-//
-// We've seen that the 'for' loop can let us perform some action
-// for every item in an array or slice.
-//
-// More recently, we discovered that it supports ranges to
-// iterate over number sequences.
-//
-// This is part of a more general capability of the `for` loop:
-// looping over one or more "objects" where an object is an
-// array, slice, or range.
-//
-// In fact, we *did* use multiple objects way back in Exercise
-// 016 where we iterated over an array and also a numeric index.
-// It didn't always work exactly this way, so the exercise had to
-// be retroactively modified a little bit.
-//
-// for (bits, 0..) |bit, i| { ... }
-//
-// The general form of a 'for' loop with two lists is:
-//
-// for (list_a, list_b) |a, b| {
-// // Here we have the first item from list_a and list_b,
-// // then the second item from each, then the third and
-// // so forth...
-// }
-//
-// What's really beautiful about this is that we don't have to
-// keep track of an index or advancing a memory pointer for
-// *either* of these lists. That error-prone stuff is all taken
-// care of for us by the compiler.
-//
-// Below, we have a program that is supposed to compare two
-// arrays. Please make it work!
-//
-const std = @import("std");
-const print = std.debug.print;
-
-pub fn main() void {
- const hex_nums = [_]u8{ 0xb, 0x2a, 0x77 };
- const dec_nums = [_]u8{ 11, 42, 119 };
-
- for (hex_nums, ???) |hn, ???| {
- if (hn != dn) {
- print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn });
- return;
- }
- }
-
- print("Arrays match!\n", .{});
-}
-//
-// You are perhaps wondering what happens if one of the two lists
-// is longer than the other? Try it!
-//
-// By the way, congratulations for making it to Exercise 100!
-//
-// +-------------+
-// | Celebration |
-// | Area * * * |
-// +-------------+
-//
-// Please keep your celebrating within the area provided.
diff --git a/exercises/101_bit_manipulation2.zig b/exercises/101_bit_manipulation2.zig
new file mode 100644
index 0000000..8b51265
--- /dev/null
+++ b/exercises/101_bit_manipulation2.zig
@@ -0,0 +1,64 @@
+//
+// Another useful application for bit manipulation is setting bits as flags.
+// This is especially useful when processing lists of something and storing
+// the states of the entries, e.g. a list of numbers and for each prime
+// number a flag is set.
+//
+// As an example, let's take the Pangram exercise from Exercism:
+// https://exercism.org/tracks/zig/exercises/pangram
+//
+// A pangram is a sentence using every letter of the alphabet at least once.
+// It is case insensitive, so it doesn't matter if a letter is lower-case
+// or upper-case. The best known English pangram is:
+//
+// "The quick brown fox jumps over the lazy dog."
+//
+// There are several ways to select the letters that appear in the pangram
+// (and it doesn't matter if they appear once or several times).
+//
+// For example, you could take an array of bool and set the value to 'true'
+// for each letter in the order of the alphabet (a=0; b=1; etc.) found in
+// the sentence. However, this is neither memory efficient nor particularly
+// fast. Instead we choose a simpler approach that is very similar in principle:
+// We define a variable with at least 26 bits (e.g. u32) and set the bit for
+// each letter that is found in the corresponding position.
+//
+// Zig provides functions for this in the standard library, but we prefer to
+// solve it without these extras, after all we want to learn something.
+//
+const std = @import("std");
+const ascii = std.ascii;
+const print = std.debug.print;
+
+pub fn main() !void {
+ // let's check the pangram
+ print("Is this a pangram? {}!\n", .{isPangram("The quick brown fox jumps over the lazy dog.")});
+}
+
+fn isPangram(str: []const u8) bool {
+ // first we check if the string has at least 26 characters
+ if (str.len < 26) return false;
+
+ // we use a 32 bit variable of which we need 26 bits
+ var bits: u32 = 0;
+
+ // loop about all characters in the string
+ for (str) |c| {
+ // if the character is an alphabetical character
+ if (ascii.isAscii(c) and ascii.isAlphabetic(c)) {
+ // then we set the bit at the position
+ //
+ // to do this, we use a little trick:
+ // since the letters in the ASCII table start at 65
+ // and are numbered sequentially, we simply subtract the
+ // first letter (in this case the 'a') from the character
+ // found, and thus get the position of the desired bit
+ bits |= @as(u32, 1) << @truncate(ascii.toLower(c) - 'a');
+ }
+ }
+ // last we return the comparison if all 26 bits are set,
+ // and if so, we know the given string is a pangram
+ //
+ // but what do we have to compare?
+ return bits == 0x..???;
+}
diff --git a/exercises/101_for5.zig b/exercises/101_for5.zig
deleted file mode 100644
index 200e71d..0000000
--- a/exercises/101_for5.zig
+++ /dev/null
@@ -1,126 +0,0 @@
-//
-// The 'for' loop is not just limited to looping over one or two
-// items. Let's try an example with a whole bunch!
-//
-// But first, there's one last thing we've avoided mentioning
-// until now: The special range that leaves off the last value:
-//
-// for ( things, 0.. ) |t, i| { ... }
-//
-// That's how we tell Zig that we want to get a numeric value for
-// every item in "things", starting with 0.
-//
-// A nice feature of these index ranges is that you can have them
-// start with any number you choose. The first value of "i" in
-// this example will be 500, then 501, 502, etc.:
-//
-// for ( things, 500.. ) |t, i| { ... }
-//
-// Remember our RPG characters? They had the following
-// properties, which we stored in a struct type:
-//
-// class
-// gold
-// experience
-//
-// What we're going to do now is store the same RPG character
-// data, but in a separate array for each property.
-//
-// It might look a little awkward, but let's bear with it.
-//
-// We've started writing a program to print a numbered list of
-// characters with each of their properties, but it needs a
-// little help:
-//
-const std = @import("std");
-const print = std.debug.print;
-
-// This is the same character role enum we've seen before.
-const Role = enum {
- wizard,
- thief,
- bard,
- warrior,
-};
-
-pub fn main() void {
- // Here are the three "property" arrays:
- const roles = [4]Role{ .wizard, .bard, .bard, .warrior };
- const gold = [4]u16{ 25, 11, 5, 7392 };
- const experience = [4]u8{ 40, 17, 55, 21 };
-
- // We would like to number our list starting with 1, not 0.
- // How do we do that?
- for (roles, gold, experience, ???) |c, g, e, i| {
- const role_name = switch (c) {
- .wizard => "Wizard",
- .thief => "Thief",
- .bard => "Bard",
- .warrior => "Warrior",
- };
-
- std.debug.print("{d}. {s} (Gold: {d}, XP: {d})\n", .{
- i,
- role_name,
- g,
- e,
- });
- }
-}
-//
-// By the way, storing our character data in arrays like this
-// isn't *just* a silly way to demonstrate multi-object 'for'
-// loops.
-//
-// It's *also* a silly way to introduce a concept called
-// "data-oriented design".
-//
-// Let's use a metaphor to build up an intuition for what this is
-// all about:
-//
-// Let's say you've been tasked with grabbing three glass
-// marbles, three spoons, and three feathers from a magic bag.
-// But you can't use your hands to grab them. Instead, you must
-// use a marble scoop, spoon magnet, and feather tongs to grab
-// each type of object.
-//
-// Now, would you rather use the magic bag:
-//
-// A. Grouped the items in clusters so you have to pick up one
-// marble, then one spoon, then one feather?
-//
-// OR
-//
-// B. Grouped the items by type so you can pick up all of the
-// marbles at once, then all the spoons, then all of the
-// feathers?
-//
-// If this metaphor is working, hopefully, it's clear that the 'B'
-// option would be much more efficient.
-//
-// Well, it probably comes as little surprise that storing and
-// using data in a sequential and uniform fashion is also more
-// efficient for modern CPUs.
-//
-// Decades of OOP practices have steered people towards grouping
-// different data types together into mixed-type "objects" with
-// the intent that these are easier on the human mind.
-// Data-oriented design groups data by type in a way that is
-// easier on the computer.
-//
-// With clever language design, maybe we can have both.
-//
-// In the Zig community, you may see the difference in groupings
-// presented with the terms "Array of Structs" (AoS) versus
-// "Struct of Arrays" (SoA).
-//
-// To envision these two designs in action, imagine an array of
-// RPG character structs, each containing three different data
-// types (AoS) versus a single RPG character struct containing
-// three arrays of one data type each, like those in the exercise
-// above (SoA).
-//
-// For a more practical application of "data-oriented design"
-// watch the following talk from Andrew Kelley, the creator of Zig:
-// https://vimeo.com/649009599
-//
diff --git a/exercises/102_formatting.zig b/exercises/102_formatting.zig
new file mode 100644
index 0000000..be16978
--- /dev/null
+++ b/exercises/102_formatting.zig
@@ -0,0 +1,140 @@
+//
+// Terminals have come a long way over the years. Starting with
+// monochrome lines on flickering CRT monitors and continuously
+// improving to today's modern terminal emulators with sharp
+// images, true color, fonts, ligatures, and characters in every
+// known language.
+//
+// Formatting our results to be appealing and allow quick visual
+// comprehension of the information is what users desire. <3
+//
+// C set string formatting standards over the years, and Zig is
+// following suit and growing daily. Due to this growth, there is
+// no official documentation for standard library features such
+// as string formatting.
+//
+// Therefore, the comments for the format() function are the only
+// way to definitively learn how to format strings in Zig:
+//
+// https://codeberg.org/ziglang/zig/src/branch/master/lib/std/Io/Writer.zig#L537
+//
+// Zig already has a very nice selection of formatting options.
+// These can be used in different ways, but generally to convert
+// numerical values into various text representations. The results
+// can be used for direct output to a terminal or stored for
+// later use or written to a file. The latter is useful when
+// large amounts of data are to be processed by other programs.
+//
+// In Ziglings, we are concerned with the output to the console.
+// But since the formatting instructions for files are the same,
+// what you learn applies universally.
+//
+// Since we write to "debug" output in Ziglings, our answers
+// usually look something like this:
+//
+// print("Text {placeholder} another text \n", .{foo});
+//
+// In addition to being replaced with foo in this example, the
+// {placeholder} in the string can also have formatting applied.
+// How does that work?
+//
+// This actually happens in several stages. In one stage, escape
+// sequences are evaluated. The one we've seen the most
+// (including the example above) is "\n" which means "line feed".
+// Whenever this statement is found, a new line is started in the
+// output. Escape sequences can also be written one after the
+// other, e.g. "\n\n" will cause two line feeds.
+//
+// By the way, the result of these escape sequences is passed
+// directly to the terminal program. Other than translating them
+// into control codes, escape sequences have nothing to do with
+// Zig. Zig knows nothing about "line feeds" or "tabs" or
+// "bells".
+//
+// The formatting that Zig *does* perform itself is found in the
+// curly brackets: "{placeholder}". Formatting instructions in
+// the placeholder will determine how the corresponding value,
+// e.g. foo, is displayed.
+//
+// And this is where it gets exciting, because format() accepts a
+// variety of formatting instructions. It's basically a tiny
+// language of its own. Here's a numeric example:
+//
+// print("Catch-0x{x:0>4}.", .{twenty_two});
+//
+// This formatting instruction outputs a hexadecimal number with
+// leading zeros:
+//
+// Catch-0x0016.
+//
+// Or you can center-align a string like so:
+//
+// print("{s:*^20}\n", .{"Hello!"});
+//
+// Output:
+//
+// *******Hello!*******
+//
+// Let's try making use of some formatting. We've decided that
+// the one thing missing from our lives is a multiplication table
+// for all numbers from 1-15. We want the table to be nice and
+// neat, with numbers in straight columns like so:
+//
+// X | 1 2 3 4 5 ...
+// ---+---+---+---+---+---+
+// 1 | 1 2 3 4 5
+//
+// 2 | 2 4 6 8 10
+//
+// 3 | 3 6 9 12 15
+//
+// 4 | 4 8 12 16 20
+//
+// 5 | 5 10 15 20 25
+//
+// ...
+//
+// Without string formatting, this would be a more challenging
+// assignment because the number of digits in the numbers varies
+// from 1 to 3. But formatting can help us with that.
+//
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn main() !void {
+ // Max number to multiply
+ const size = 15;
+
+ // Print the header:
+ //
+ // We start with a single 'X' for the diagonal.
+ print("\n X |", .{});
+
+ // Header row with all numbers from 1 to size.
+ for (0..size) |n| {
+ print("{d:>3} ", .{n + 1});
+ }
+ print("\n", .{});
+
+ // Header column rule line.
+ var n: u8 = 0;
+ while (n <= size) : (n += 1) {
+ print("---+", .{});
+ }
+ print("\n", .{});
+
+ // Now the actual table. (Is there anything more beautiful
+ // than a well-formatted table?)
+ for (0..size) |a| {
+ print("{d:>2} |", .{a + 1});
+
+ for (0..size) |b| {
+ // What formatting is needed here to make our columns
+ // nice and straight?
+ print("{???} ", .{(a + 1) * (b + 1)});
+ }
+
+ // After each row we use double line feed:
+ print("\n\n", .{});
+ }
+}
diff --git a/exercises/102_testing.zig b/exercises/102_testing.zig
deleted file mode 100644
index 0dec9b9..0000000
--- a/exercises/102_testing.zig
+++ /dev/null
@@ -1,96 +0,0 @@
-//
-// A big advantage of Zig is the integration of its own test system.
-// This allows the philosophy of Test Driven Development (TDD) to be
-// implemented perfectly. Zig even goes one step further than other
-// languages, the tests can be included directly in the source file.
-//
-// This has several advantages. On the one hand it is much clearer to
-// have everything in one file, both the source code and the associated
-// test code. On the other hand, it is much easier for third parties
-// to understand what exactly a function is supposed to do if they can
-// simply look at the test inside the source and compare both.
-//
-// Especially if you want to understand how e.g. the standard library
-// of Zig works, this approach is very helpful. Furthermore it is very
-// practical, if you want to report a bug to the Zig community, to
-// illustrate it with a small example including a test.
-//
-// Therefore, in this exercise we will deal with the basics of testing
-// in Zig. Basically, tests work as follows: you pass certain parameters
-// to a function, for which you get a return - the result. This is then
-// compared with the EXPECTED value. If both values match, the test is
-// passed, otherwise an error message is displayed.
-//
-// testing.expect(foo(param1, param2) == expected);
-//
-// Also other comparisons are possible, deviations or also errors can
-// be provoked, which must lead to an appropriate behavior of the
-// function, so that the test is passed.
-//
-// Tests can be run via Zig build system or applied directly to
-// individual modules using "zig test xyz.zig".
-//
-// Both can be used script-driven to execute tests automatically, e.g.
-// after checking into a Git repository. Something we also make extensive
-// use of here at Ziglings.
-//
-const std = @import("std");
-const testing = std.testing;
-
-// This is a simple function that builds a sum from the passed parameters and
-// returns.
-fn add(a: f16, b: f16) f16 {
- return a + b;
-}
-
-// The associated test. It always starts with the keyword "test", followed by a
-// description of the tasks of the test. This is followed by the test cases in
-// curly brackets.
-test "add" {
-
- // The first test checks if the sum of '41' and '1' gives '42', which is
- // correct.
- try testing.expect(add(41, 1) == 42);
-
- // Another way to perform this test is as follows:
- try testing.expectEqual(42, add(41, 1));
-
- // This time a test with the addition of a negative number:
- try testing.expect(add(5, -4) == 1);
-
- // And a floating point operation:
- try testing.expect(add(1.5, 1.5) == 3);
-}
-
-// Another simple function that returns the result of subtracting the two
-// parameters.
-fn sub(a: f16, b: f16) f16 {
- return a - b;
-}
-
-// The corresponding test is not much different from the previous one. Except
-// that it contains an error that you need to correct.
-test "sub" {
- try testing.expect(sub(10, 5) == 6);
-
- try testing.expect(sub(3, 1.5) == 1.5);
-}
-
-// This function divides the numerator by the denominator. Here it is important
-// that the denominator must not be zero. This is checked and if it occurs an
-// error is returned.
-fn divide(a: f16, b: f16) !f16 {
- if (b == 0) return error.DivisionByZero;
- return a / b;
-}
-
-test "divide" {
- try testing.expect(divide(2, 2) catch unreachable == 1);
- try testing.expect(divide(-1, -1) catch unreachable == 1);
- try testing.expect(divide(10, 2) catch unreachable == 5);
- try testing.expect(divide(1, 3) catch unreachable == 0.3333333333333333);
-
- // Now we test if the function returns an error if we pass a zero as
- // denominator. But which error needs to be tested?
- try testing.expectError(error.???, divide(15, 0));
-}
diff --git a/exercises/103_for4.zig b/exercises/103_for4.zig
new file mode 100644
index 0000000..c8a1161
--- /dev/null
+++ b/exercises/103_for4.zig
@@ -0,0 +1,62 @@
+//
+// We've seen that the 'for' loop can let us perform some action
+// for every item in an array or slice.
+//
+// More recently, we discovered that it supports ranges to
+// iterate over number sequences.
+//
+// This is part of a more general capability of the `for` loop:
+// looping over one or more "objects" where an object is an
+// array, slice, or range.
+//
+// In fact, we *did* use multiple objects way back in Exercise
+// 016 where we iterated over an array and also a numeric index.
+// It didn't always work exactly this way, so the exercise had to
+// be retroactively modified a little bit.
+//
+// for (bits, 0..) |bit, i| { ... }
+//
+// The general form of a 'for' loop with two lists is:
+//
+// for (list_a, list_b) |a, b| {
+// // Here we have the first item from list_a and list_b,
+// // then the second item from each, then the third and
+// // so forth...
+// }
+//
+// What's really beautiful about this is that we don't have to
+// keep track of an index or advancing a memory pointer for
+// *either* of these lists. That error-prone stuff is all taken
+// care of for us by the compiler.
+//
+// Below, we have a program that is supposed to compare two
+// arrays. Please make it work!
+//
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn main() void {
+ const hex_nums = [_]u8{ 0xb, 0x2a, 0x77 };
+ const dec_nums = [_]u8{ 11, 42, 119 };
+
+ for (hex_nums, ???) |hn, ???| {
+ if (hn != dn) {
+ print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn });
+ return;
+ }
+ }
+
+ print("Arrays match!\n", .{});
+}
+//
+// You are perhaps wondering what happens if one of the two lists
+// is longer than the other? Try it!
+//
+// By the way, congratulations for making it to Exercise 100!
+//
+// +-------------+
+// | Celebration |
+// | Area * * * |
+// +-------------+
+//
+// Please keep your celebrating within the area provided.
diff --git a/exercises/103_tokenization.zig b/exercises/103_tokenization.zig
deleted file mode 100644
index d0abb5c..0000000
--- a/exercises/103_tokenization.zig
+++ /dev/null
@@ -1,148 +0,0 @@
-//
-// The functionality of the standard library is becoming increasingly
-// important in Zig. First of all, it is helpful to take a look at how
-// the individual functions are implemented. Because this is wonderfully
-// suitable as a template for your own functions. In addition, these
-// standard functions are part of the basic configuration of Zig.
-//
-// This means that they are always available on every system.
-// Therefore it is worthwhile to deal with them also in Ziglings.
-// It's a great way to learn important skills. For example, it is
-// often necessary to process large amounts of data from files.
-// And for this sequential reading and processing, Zig provides some
-// useful functions, which we will take a closer look at in the coming
-// exercises.
-//
-// A nice example of this has been published on the Zig homepage,
-// replacing the somewhat dusty 'Hello world!
-//
-// Nothing against 'Hello world!', but it just doesn't do justice
-// to the elegance of Zig and that's a pity, if someone takes a short,
-// first look at the homepage and doesn't get 'enchanted'. And for that
-// the present example is simply better suited and we will therefore
-// use it as an introduction to tokenizing, because it is wonderfully
-// suited to understand the basic principles.
-//
-// In the following exercises we will also read and process data from
-// large files, it will then be clearer to you how useful all this is.
-//
-// Let's start with the analysis of the example from the Zig homepage
-// and explain the most important things.
-//
-// const std = @import("std");
-//
-// // Here a function from the Standard library is defined,
-// // which transfers numbers from a string into the respective
-// // integer values.
-// const parseInt = std.fmt.parseInt;
-//
-// // Defining a test case
-// test "parse integers" {
-//
-// // Four numbers are passed in a string.
-// // Please note that the individual values are separated
-// // either by a space or a comma.
-// const input = "123 67 89,99";
-//
-// // In order to be able to process the input values,
-// // memory is required. An allocator is defined here for
-// // this purpose.
-// const gpa = std.testing.allocator;
-//
-// // An array into which the numbers are stored is initialized.
-// var list: std.ArrayList(u32) = .empty;
-//
-// // This way you can never forget what is urgently needed
-// // and the compiler doesn't grumble either.
-// defer list.deinit(gpa);
-//
-// // Now it gets exciting:
-// // A standard tokenizer is called (Zig has several) and
-// // used to locate the positions of the respective separators
-// // (we remember, space and comma) and pass them to an iterator.
-// var it = std.mem.tokenizeAny(u8, input, " ,");
-//
-// // The iterator can now be processed in a loop and the
-// // individual numbers can be transferred.
-// while (it.next()) |num| {
-// // But be careful: The numbers are still only available
-// // as strings. This is where the integer parser comes
-// // into play, converting them into real integer values.
-// const n = try parseInt(u32, num, 10);
-//
-// // Finally the individual values are stored in the array.
-// try list.append(gpa, n);
-// }
-//
-// // For the subsequent test, a second static array is created,
-// // which is directly filled with the expected values.
-// const expected = [_]u32{ 123, 67, 89, 99 };
-//
-// // Now the numbers converted from the string can be compared
-// // with the expected ones, so that the test is completed
-// // successfully.
-// for (expected, list.items) |exp, actual| {
-// try std.testing.expectEqual(exp, actual);
-// }
-// }
-//
-// So much for the example from the homepage.
-// Let's summarize the basic steps again:
-//
-// - We have a set of data in sequential order, separated from each other
-// by means of various characters.
-//
-// - For further processing, for example in an array, this data must be
-// read in, separated and, if necessary, converted into the target format.
-//
-// - We need a buffer that is large enough to hold the data.
-//
-// - This buffer can be created either statically at compile time, if the
-// amount of data is already known, or dynamically at runtime by using
-// a memory allocator.
-//
-// - The data are divided by means of Tokenizer at the respective
-// separators and stored in the reserved memory. This usually also
-// includes conversion to the target format.
-//
-// - Now the data can be conveniently processed further in the correct format.
-//
-// These steps are basically always the same.
-// Whether the data is read from a file or entered by the user via the
-// keyboard, for example, is irrelevant. Only subtleties are distinguished
-// and that's why Zig has different tokenizers. But more about this in
-// later exercises.
-//
-// Now we also want to write a small program to tokenize some data,
-// after all we need some practice. Suppose we want to count the words
-// of this little poem:
-//
-// My name is Ozymandias, King of Kings;
-// Look on my Works, ye Mighty, and despair!
-// by Percy Bysshe Shelley
-//
-//
-const std = @import("std");
-const print = std.debug.print;
-
-pub fn main() !void {
-
- // our input
- const poem =
- \\My name is Ozymandias, King of Kings;
- \\Look on my Works, ye Mighty, and despair!
- ;
-
- // now the tokenizer, but what do we need here?
- var it = std.mem.tokenizeAny(u8, poem, ???);
-
- // print all words and count them
- var cnt: usize = 0;
- while (it.next()) |word| {
- cnt += 1;
- print("{s}\n", .{word});
- }
-
- // print the result
- print("This little poem has {d} words!\n", .{cnt});
-}
diff --git a/exercises/104_for5.zig b/exercises/104_for5.zig
new file mode 100644
index 0000000..200e71d
--- /dev/null
+++ b/exercises/104_for5.zig
@@ -0,0 +1,126 @@
+//
+// The 'for' loop is not just limited to looping over one or two
+// items. Let's try an example with a whole bunch!
+//
+// But first, there's one last thing we've avoided mentioning
+// until now: The special range that leaves off the last value:
+//
+// for ( things, 0.. ) |t, i| { ... }
+//
+// That's how we tell Zig that we want to get a numeric value for
+// every item in "things", starting with 0.
+//
+// A nice feature of these index ranges is that you can have them
+// start with any number you choose. The first value of "i" in
+// this example will be 500, then 501, 502, etc.:
+//
+// for ( things, 500.. ) |t, i| { ... }
+//
+// Remember our RPG characters? They had the following
+// properties, which we stored in a struct type:
+//
+// class
+// gold
+// experience
+//
+// What we're going to do now is store the same RPG character
+// data, but in a separate array for each property.
+//
+// It might look a little awkward, but let's bear with it.
+//
+// We've started writing a program to print a numbered list of
+// characters with each of their properties, but it needs a
+// little help:
+//
+const std = @import("std");
+const print = std.debug.print;
+
+// This is the same character role enum we've seen before.
+const Role = enum {
+ wizard,
+ thief,
+ bard,
+ warrior,
+};
+
+pub fn main() void {
+ // Here are the three "property" arrays:
+ const roles = [4]Role{ .wizard, .bard, .bard, .warrior };
+ const gold = [4]u16{ 25, 11, 5, 7392 };
+ const experience = [4]u8{ 40, 17, 55, 21 };
+
+ // We would like to number our list starting with 1, not 0.
+ // How do we do that?
+ for (roles, gold, experience, ???) |c, g, e, i| {
+ const role_name = switch (c) {
+ .wizard => "Wizard",
+ .thief => "Thief",
+ .bard => "Bard",
+ .warrior => "Warrior",
+ };
+
+ std.debug.print("{d}. {s} (Gold: {d}, XP: {d})\n", .{
+ i,
+ role_name,
+ g,
+ e,
+ });
+ }
+}
+//
+// By the way, storing our character data in arrays like this
+// isn't *just* a silly way to demonstrate multi-object 'for'
+// loops.
+//
+// It's *also* a silly way to introduce a concept called
+// "data-oriented design".
+//
+// Let's use a metaphor to build up an intuition for what this is
+// all about:
+//
+// Let's say you've been tasked with grabbing three glass
+// marbles, three spoons, and three feathers from a magic bag.
+// But you can't use your hands to grab them. Instead, you must
+// use a marble scoop, spoon magnet, and feather tongs to grab
+// each type of object.
+//
+// Now, would you rather use the magic bag:
+//
+// A. Grouped the items in clusters so you have to pick up one
+// marble, then one spoon, then one feather?
+//
+// OR
+//
+// B. Grouped the items by type so you can pick up all of the
+// marbles at once, then all the spoons, then all of the
+// feathers?
+//
+// If this metaphor is working, hopefully, it's clear that the 'B'
+// option would be much more efficient.
+//
+// Well, it probably comes as little surprise that storing and
+// using data in a sequential and uniform fashion is also more
+// efficient for modern CPUs.
+//
+// Decades of OOP practices have steered people towards grouping
+// different data types together into mixed-type "objects" with
+// the intent that these are easier on the human mind.
+// Data-oriented design groups data by type in a way that is
+// easier on the computer.
+//
+// With clever language design, maybe we can have both.
+//
+// In the Zig community, you may see the difference in groupings
+// presented with the terms "Array of Structs" (AoS) versus
+// "Struct of Arrays" (SoA).
+//
+// To envision these two designs in action, imagine an array of
+// RPG character structs, each containing three different data
+// types (AoS) versus a single RPG character struct containing
+// three arrays of one data type each, like those in the exercise
+// above (SoA).
+//
+// For a more practical application of "data-oriented design"
+// watch the following talk from Andrew Kelley, the creator of Zig:
+// https://vimeo.com/649009599
+//
diff --git a/exercises/104_threading.zig b/exercises/104_threading.zig
deleted file mode 100644
index 3c3fa21..0000000
--- a/exercises/104_threading.zig
+++ /dev/null
@@ -1,129 +0,0 @@
-//
-// 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.
-//
-// 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
-//
-// 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.
-//
-// The following diagram roughly illustrates the difference between
-// the various types of process execution:
-//
-//
-// Synchronous Asynchronous
-// Processing Processing Multithreading
-// ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
-// │ Thread 1 │ │ Thread 1 │ │ Thread 1 │ │ Thread 2 │
-// ├──────────┤ ├──────────┤ ├──────────┤ ├──────────┤ Overall Time
-// └──┼┼┼┼┼───┴─┴──┼┼┼┼┼───┴──┴──┼┼┼┼┼───┴─┴──┼┼┼┼┼───┴──┬───────┬───────┬──
-// ├───┤ ├───┤ ├───┤ ├───┤ │ │ │
-// │ T │ │ T │ │ T │ │ T │ │ │ │
-// │ a │ │ a │ │ a │ │ a │ │ │ │
-// │ s │ │ s │ │ s │ │ s │ │ │ │
-// │ k │ │ k │ │ k │ │ k │ │ │ │
-// │ │ │ │ │ │ │ │ │ │ │
-// │ 1 │ │ 1 │ │ 1 │ │ 3 │ │ │ │
-// └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ │ │ │
-// │ │ │ │ 5 Sec │ │
-// ┌────┴───┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ │ │ │
-// │Blocking│ │ T │ │ T │ │ T │ │ │ │
-// └────┬───┘ │ a │ │ a │ │ a │ │ │ │
-// │ │ s │ │ s │ │ s │ │ 8 Sec │
-// ┌─┴─┐ │ k │ │ k │ │ k │ │ │ │
-// │ T │ │ │ │ │ │ │ │ │ │
-// │ a │ │ 2 │ │ 2 │ │ 4 │ │ │ │
-// │ s │ └─┬─┘ ├───┤ ├───┤ │ │ │
-// │ k │ │ │┼┼┼│ │┼┼┼│ ▼ │ 10 Sec
-// │ │ ┌─┴─┐ └───┴────────┴───┴───────── │ │
-// │ 1 │ │ T │ │ │
-// └─┬─┘ │ a │ │ │
-// │ │ s │ │ │
-// ┌─┴─┐ │ k │ │ │
-// │ T │ │ │ │ │
-// │ a │ │ 1 │ │ │
-// │ s │ ├───┤ │ │
-// │ k │ │┼┼┼│ ▼ │
-// │ │ └───┴──────────────────────────────────────────── │
-// │ 2 │ │
-// ├───┤ │
-// │┼┼┼│ ▼
-// └───┴────────────────────────────────────────────────────────────────
-//
-//
-// The diagram was modeled on the one in a blog in which the differences
-// between asynchronous processing and multithreading are explained in detail:
-// https://blog.devgenius.io/multi-threading-vs-asynchronous-programming-what-is-the-difference-3ebfe1179a5
-//
-// Our exercise is essentially about clarifying the approach in Zig and
-// therefore we try to keep it as simple as possible.
-// Multithreading in itself is already difficult enough. ;-)
-//
-const std = @import("std");
-
-pub fn main() !void {
- // This is where the preparatory work takes place
- // before the parallel processing begins.
- std.debug.print("Starting work...\n", .{});
-
- // These curly brackets are very important, they are necessary
- // to enclose the area where the threads are called.
- // Without these brackets, the program would not wait for the
- // end of the threads and they would continue to run beyond the
- // end of the program.
- {
- // Now we start the first thread, with the number as parameter
- const handle = try std.Thread.spawn(.{}, thread_function, .{1});
-
- // Waits for the thread to complete,
- // then deallocates any resources created on `spawn()`.
- defer handle.join();
-
- // Second thread
- const handle2 = try std.Thread.spawn(.{}, thread_function, .{-4}); // that can't be right?
- defer handle2.join();
-
- // Third thread
- const handle3 = try std.Thread.spawn(.{}, thread_function, .{3});
- defer ??? // <-- something is missing
-
- // After the threads have been started,
- // 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.fromMilliseconds(400), .awake);
- std.debug.print("Some weird stuff, after starting the threads.\n", .{});
- }
- // After we have left the closed area, we wait until
- // the threads have run through, if this has not yet been the case.
- std.debug.print("Zig is cool!\n", .{});
-}
-
-// 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(id: usize) !void {
- var io_instance: std.Io.Threaded = .init_single_threaded;
- const io = io_instance.io();
- 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 = 300 * ((5 - id % 3) - 2);
- try io.sleep(std.Io.Duration.fromMilliseconds(@intCast(work_time)), .awake);
-
- 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,
-// e.g. by setting up a pool and allowing the threads to communicate
-// with each other using semaphores.
-//
-// But that's a topic for another exercise.
diff --git a/exercises/105_testing.zig b/exercises/105_testing.zig
new file mode 100644
index 0000000..0dec9b9
--- /dev/null
+++ b/exercises/105_testing.zig
@@ -0,0 +1,96 @@
+//
+// A big advantage of Zig is the integration of its own test system.
+// This allows the philosophy of Test Driven Development (TDD) to be
+// implemented perfectly. Zig even goes one step further than other
+// languages, the tests can be included directly in the source file.
+//
+// This has several advantages. On the one hand it is much clearer to
+// have everything in one file, both the source code and the associated
+// test code. On the other hand, it is much easier for third parties
+// to understand what exactly a function is supposed to do if they can
+// simply look at the test inside the source and compare both.
+//
+// Especially if you want to understand how e.g. the standard library
+// of Zig works, this approach is very helpful. Furthermore it is very
+// practical, if you want to report a bug to the Zig community, to
+// illustrate it with a small example including a test.
+//
+// Therefore, in this exercise we will deal with the basics of testing
+// in Zig. Basically, tests work as follows: you pass certain parameters
+// to a function, for which you get a return - the result. This is then
+// compared with the EXPECTED value. If both values match, the test is
+// passed, otherwise an error message is displayed.
+//
+// testing.expect(foo(param1, param2) == expected);
+//
+// Also other comparisons are possible, deviations or also errors can
+// be provoked, which must lead to an appropriate behavior of the
+// function, so that the test is passed.
+//
+// Tests can be run via Zig build system or applied directly to
+// individual modules using "zig test xyz.zig".
+//
+// Both can be used script-driven to execute tests automatically, e.g.
+// after checking into a Git repository. Something we also make extensive
+// use of here at Ziglings.
+//
+const std = @import("std");
+const testing = std.testing;
+
+// This is a simple function that builds a sum from the passed parameters and
+// returns.
+fn add(a: f16, b: f16) f16 {
+ return a + b;
+}
+
+// The associated test. It always starts with the keyword "test", followed by a
+// description of the tasks of the test. This is followed by the test cases in
+// curly brackets.
+test "add" {
+
+ // The first test checks if the sum of '41' and '1' gives '42', which is
+ // correct.
+ try testing.expect(add(41, 1) == 42);
+
+ // Another way to perform this test is as follows:
+ try testing.expectEqual(42, add(41, 1));
+
+ // This time a test with the addition of a negative number:
+ try testing.expect(add(5, -4) == 1);
+
+ // And a floating point operation:
+ try testing.expect(add(1.5, 1.5) == 3);
+}
+
+// Another simple function that returns the result of subtracting the two
+// parameters.
+fn sub(a: f16, b: f16) f16 {
+ return a - b;
+}
+
+// The corresponding test is not much different from the previous one. Except
+// that it contains an error that you need to correct.
+test "sub" {
+ try testing.expect(sub(10, 5) == 6);
+
+ try testing.expect(sub(3, 1.5) == 1.5);
+}
+
+// This function divides the numerator by the denominator. Here it is important
+// that the denominator must not be zero. This is checked and if it occurs an
+// error is returned.
+fn divide(a: f16, b: f16) !f16 {
+ if (b == 0) return error.DivisionByZero;
+ return a / b;
+}
+
+test "divide" {
+ try testing.expect(divide(2, 2) catch unreachable == 1);
+ try testing.expect(divide(-1, -1) catch unreachable == 1);
+ try testing.expect(divide(10, 2) catch unreachable == 5);
+ try testing.expect(divide(1, 3) catch unreachable == 0.3333333333333333);
+
+ // Now we test if the function returns an error if we pass a zero as
+ // denominator. But which error needs to be tested?
+ try testing.expectError(error.???, divide(15, 0));
+}
diff --git a/exercises/105_threading2.zig b/exercises/105_threading2.zig
deleted file mode 100644
index 374391a..0000000
--- a/exercises/105_threading2.zig
+++ /dev/null
@@ -1,107 +0,0 @@
-//
-// Now that we are familiar with the principles of multi-threading,
-// let's boldly venture into a practical example from mathematics.
-// We will determine the circle number PI with sufficient accuracy.
-//
-// There are different methods for this, and some of them are several
-// hundred years old. For us, the dusty procedures are surprisingly well
-// suited to our exercise. Because the mathematicians of the time didn't
-// have fancy computers with which we can calculate something like this
-// in seconds today.
-// Whereby, of course, it depends on the accuracy, i.e. how many digits
-// after the decimal point we are interested in.
-// But these old procedures can still be tackled with paper and pencil,
-// which is why they are easier for us to understand.
-// At least for me. ;-)
-//
-// So let's take a mental leap back a few years.
-// Around 1672 (if you want to know and read about it in detail, you can
-// do so on Wikipedia, for example), various mathematicians once again
-// discovered a method of approaching the circle number PI.
-// There were the Scottish mathematician Gregory and the German
-// mathematician Leibniz, and even a few hundred years earlier the Indian
-// mathematician Madhava. All of them independently developed the same
-// formula, which was published by Leibniz in 1682 in the journal
-// "Acta Eruditorum".
-// This is why this method has become known as the "Leibniz series",
-// although the other names are also often used today.
-// We will not go into the formula and its derivation in detail, but
-// will deal with the series straight away:
-//
-// 4 4 4 4 4
-// PI = --- - --- + --- - --- + --- ...
-// 1 3 5 7 9
-//
-// As you can clearly see, the series starts with the whole number 4 and
-// approaches the circle number by subtracting and adding smaller and
-// smaller parts of 4. Pretty much everyone has learned PI = 3.14 at school,
-// but very few people remember other digits, and this is rarely necessary
-// in practice. Because either you don't need the precision, or you use a
-// calculator in which the number is stored as a very precise constant.
-// But at some point this constant was calculated and we are doing the same
-// now. The question at this point is, how many partial values do we have
-// to calculate for which accuracy?
-//
-// The answer is chewing, to get 8 digits after the decimal point we need
-// 1,000,000,000 partial values. And for each additional digit we have to
-// add a zero.
-// Even fast computers - and I mean really fast computers - get a bit warmer
-// on the CPU when it comes to really many digits. But the 8 digits are
-// enough for us for now, because we want to understand the principle and
-// nothing more, right?
-//
-// As we have already discovered, the Leibniz series is a series with a
-// fixed distance of 2 between the individual partial values. This makes
-// it easy to apply a simple loop to it, because if we start with n = 1
-// (which is not necessarily useful now) we always have to add 2 in each
-// round.
-// But wait! The partial values are alternately added and subtracted.
-// This could also be achieved with one loop, but not very elegantly.
-// It also makes sense to split this between two CPUs, one calculates
-// the positive values and the other the negative values. And so we can
-// simply start two threads and add everything up at the end and we're
-// done.
-// We just have to remember that if only the positive or negative values
-// are calculated, the distances are twice as large, i.e. 4.
-//
-// So that the whole thing has a real learning effect, the first thread
-// call is specified and you have to make the second.
-// But don't worry, it will work out. :-)
-//
-const std = @import("std");
-
-pub fn main() !void {
- const count = 1_000_000_000;
- var pi_plus: f64 = 0;
- var pi_minus: f64 = 0;
-
- {
- // First thread to calculate the plus numbers.
- const handle1 = try std.Thread.spawn(.{}, thread_pi, .{ &pi_plus, 5, count });
- defer handle1.join();
-
- // Second thread to calculate the minus numbers.
- ???
-
- }
- // Here we add up the results.
- std.debug.print("PI ≈ {d:.8}\n", .{4 + pi_plus - pi_minus});
-}
-
-fn thread_pi(pi: *f64, begin: u64, end: u64) !void {
- var n: u64 = begin;
- while (n < end) : (n += 4) {
- pi.* += 4 / @as(f64, @floatFromInt(n));
- }
-}
-// If you wish, you can increase the number of loop passes, which
-// improves the number of digits.
-//
-// But be careful:
-// In order for parallel processing to really show its strengths,
-// the compiler must be given the "-O ReleaseFast" flag when it
-// is created. Otherwise the debug functions slow down the speed
-// to such an extent that seconds become minutes during execution.
-//
-// And you should remove the formatting restriction in "print",
-// otherwise you will not be able to see the additional digits.
diff --git a/exercises/106_files.zig b/exercises/106_files.zig
deleted file mode 100644
index bf4de9b..0000000
--- a/exercises/106_files.zig
+++ /dev/null
@@ -1,102 +0,0 @@
-//
-// Until now, we've only been printing our output in the console,
-// which is good enough for fighting aliens and hermit bookkeeping.
-//
-// However, many other tasks require some interaction with the file system,
-// which is the underlying structure for organizing files on your computer.
-//
-// The file system provides a hierarchical structure for storing files
-// by organizing them into directories, which hold files and other directories,
-// thus creating a tree structure that can be navigated.
-//
-// Fortunately, the Zig Standard Library provides a simple API for interacting
-// with the file system, see the detail documentation here:
-//
-// https://ziglang.org/documentation/master/std/#std.Io
-//
-// In this exercise, we'll try to:
-// - create a new directory,
-// - open a file in the directory,
-// - write to the file.
-//
-// Note: For simplicity, we write byte-by-byte without buffering.
-// In real applications, you'd typically use a buffer for better
-// performance. We'll learn about buffered I/O in a later exercise.
-//
-const std = @import("std");
-
-pub fn main(init: std.process.Init) !void {
- // default I/O implementation
- const io = init.io;
-
- // first we get the current working directory
- const cwd: std.Io.Dir = std.Io.Dir.cwd();
-
- // then we'll try to make a new directory /output/
- // to store our output files.
- cwd.createDir(io, "output", .default_dir) catch |e| switch (e) {
- // there is a chance you might want to run this
- // program more than once and the path might already
- // have been created, so we'll have to handle this error
- // by doing nothing
- //
- // we want to catch error.PathAlreadyExists and do nothing
- ??? => {},
- // if there's any other unexpected error we just propagate it through
- else => return e,
- };
-
- // then we'll try to open our freshly created directory
- // wait a minute...
- // opening a directory might fail!
- // what should we do here?
- var output_dir: std.Io.Dir = try cwd.openDir(io, "output", .{});
- defer output_dir.close(io);
-
- // we try to open the file `zigling.txt`,
- // and propagate any error up
- const file: std.Io.File = try output_dir.createFile(io, "zigling.txt", .{});
- // it is a good habit to close a file after you are done with it
- // so that other programs can read it and prevent data corruption
- // but here we are not yet done writing to the file
- // if only there were a keyword in Zig that
- // allowed you to "defer" code execution to the end of the scope...
- file.close(io);
-
- // you are not allowed to move these lines above the file closing line!
- var file_writer = file.writer(io, &.{});
- const writer = &file_writer.interface;
-
- const byte_written = try writer.write("It's zigling time!");
- std.debug.print("Successfully wrote {d} bytes.\n", .{byte_written});
-}
-// to check if you actually write to the file, you can either,
-// 1. open the file in your text editor, or
-// 2. print the content of the file in the console with one of these commands
-// Linux/macOS: >> cat ./output/zigling.txt
-// Windows (CMD): >> type .\output\zigling.txt
-//
-//
-// More on Creating files
-//
-// notice in:
-// ... try output_dir.createFile(io, "zigling.txt", .{});
-// ^^^
-// we passed this anonymous struct to the function call
-//
-// this is the struct `CreateFlag` with default fields
-// {
-// read: bool = false,
-// truncate: bool = true,
-// exclusive: bool = false,
-// lock: Lock = .none,
-// lock_nonblocking: bool = false,
-// mode: Mode = default_mode
-// }
-//
-// Question:
-// - what should you do if you want to also read the file after opening it?
-// - go to the documentation of the struct `std.Io.Dir` here:
-// https://ziglang.org/documentation/master/std/#std.Io.Dir
-// - can you find a function for opening a file? how about deleting a file?
-// - what kind of options can you use with those functions?
diff --git a/exercises/106_tokenization.zig b/exercises/106_tokenization.zig
new file mode 100644
index 0000000..d0abb5c
--- /dev/null
+++ b/exercises/106_tokenization.zig
@@ -0,0 +1,148 @@
+//
+// The functionality of the standard library is becoming increasingly
+// important in Zig. First of all, it is helpful to take a look at how
+// the individual functions are implemented. Because this is wonderfully
+// suitable as a template for your own functions. In addition, these
+// standard functions are part of the basic configuration of Zig.
+//
+// This means that they are always available on every system.
+// Therefore it is worthwhile to deal with them also in Ziglings.
+// It's a great way to learn important skills. For example, it is
+// often necessary to process large amounts of data from files.
+// And for this sequential reading and processing, Zig provides some
+// useful functions, which we will take a closer look at in the coming
+// exercises.
+//
+// A nice example of this has been published on the Zig homepage,
+// replacing the somewhat dusty 'Hello world!
+//
+// Nothing against 'Hello world!', but it just doesn't do justice
+// to the elegance of Zig and that's a pity, if someone takes a short,
+// first look at the homepage and doesn't get 'enchanted'. And for that
+// the present example is simply better suited and we will therefore
+// use it as an introduction to tokenizing, because it is wonderfully
+// suited to understand the basic principles.
+//
+// In the following exercises we will also read and process data from
+// large files, it will then be clearer to you how useful all this is.
+//
+// Let's start with the analysis of the example from the Zig homepage
+// and explain the most important things.
+//
+// const std = @import("std");
+//
+// // Here a function from the Standard library is defined,
+// // which transfers numbers from a string into the respective
+// // integer values.
+// const parseInt = std.fmt.parseInt;
+//
+// // Defining a test case
+// test "parse integers" {
+//
+// // Four numbers are passed in a string.
+// // Please note that the individual values are separated
+// // either by a space or a comma.
+// const input = "123 67 89,99";
+//
+// // In order to be able to process the input values,
+// // memory is required. An allocator is defined here for
+// // this purpose.
+// const gpa = std.testing.allocator;
+//
+// // An array into which the numbers are stored is initialized.
+// var list: std.ArrayList(u32) = .empty;
+//
+// // This way you can never forget what is urgently needed
+// // and the compiler doesn't grumble either.
+// defer list.deinit(gpa);
+//
+// // Now it gets exciting:
+// // A standard tokenizer is called (Zig has several) and
+// // used to locate the positions of the respective separators
+// // (we remember, space and comma) and pass them to an iterator.
+// var it = std.mem.tokenizeAny(u8, input, " ,");
+//
+// // The iterator can now be processed in a loop and the
+// // individual numbers can be transferred.
+// while (it.next()) |num| {
+// // But be careful: The numbers are still only available
+// // as strings. This is where the integer parser comes
+// // into play, converting them into real integer values.
+// const n = try parseInt(u32, num, 10);
+//
+// // Finally the individual values are stored in the array.
+// try list.append(gpa, n);
+// }
+//
+// // For the subsequent test, a second static array is created,
+// // which is directly filled with the expected values.
+// const expected = [_]u32{ 123, 67, 89, 99 };
+//
+// // Now the numbers converted from the string can be compared
+// // with the expected ones, so that the test is completed
+// // successfully.
+// for (expected, list.items) |exp, actual| {
+// try std.testing.expectEqual(exp, actual);
+// }
+// }
+//
+// So much for the example from the homepage.
+// Let's summarize the basic steps again:
+//
+// - We have a set of data in sequential order, separated from each other
+// by means of various characters.
+//
+// - For further processing, for example in an array, this data must be
+// read in, separated and, if necessary, converted into the target format.
+//
+// - We need a buffer that is large enough to hold the data.
+//
+// - This buffer can be created either statically at compile time, if the
+// amount of data is already known, or dynamically at runtime by using
+// a memory allocator.
+//
+// - The data are divided by means of Tokenizer at the respective
+// separators and stored in the reserved memory. This usually also
+// includes conversion to the target format.
+//
+// - Now the data can be conveniently processed further in the correct format.
+//
+// These steps are basically always the same.
+// Whether the data is read from a file or entered by the user via the
+// keyboard, for example, is irrelevant. Only subtleties are distinguished
+// and that's why Zig has different tokenizers. But more about this in
+// later exercises.
+//
+// Now we also want to write a small program to tokenize some data,
+// after all we need some practice. Suppose we want to count the words
+// of this little poem:
+//
+// My name is Ozymandias, King of Kings;
+// Look on my Works, ye Mighty, and despair!
+// by Percy Bysshe Shelley
+//
+//
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn main() !void {
+
+ // our input
+ const poem =
+ \\My name is Ozymandias, King of Kings;
+ \\Look on my Works, ye Mighty, and despair!
+ ;
+
+ // now the tokenizer, but what do we need here?
+ var it = std.mem.tokenizeAny(u8, poem, ???);
+
+ // print all words and count them
+ var cnt: usize = 0;
+ while (it.next()) |word| {
+ cnt += 1;
+ print("{s}\n", .{word});
+ }
+
+ // print the result
+ print("This little poem has {d} words!\n", .{cnt});
+}
diff --git a/exercises/107_files2.zig b/exercises/107_files2.zig
deleted file mode 100644
index c363086..0000000
--- a/exercises/107_files2.zig
+++ /dev/null
@@ -1,61 +0,0 @@
-//
-// Prerequisite :
-// - exercise/106_files.zig, or
-// - create a file {project_root}/output/zigling.txt
-// with content `It's zigling time!`(18 bytes total)
-//
-// Now there's no point in writing to a file if we don't read from it, am I right?
-// Let's write a program to read the content of the file that we just created.
-//
-// I am assuming that you've created the appropriate files for this to work.
-//
-// Alright, bud, lean in close. Here's the game plan.
-// - First, we open the {project_root}/output/ directory
-// - Secondly, we open file `zigling.txt` in that directory
-// - Then, we initialize an array of characters with all letter 'A', and print it
-// - After that, we read the content of the file into the array
-// - Finally, we print out the content we just read
-//
-// Note: For simplicity, we read byte-by-byte without buffering.
-// In real applications, you'd typically use a buffer for better
-// performance. We'll learn about buffered I/O in a later exercise.
-
-const std = @import("std");
-
-pub fn main(init: std.process.Init) !void {
- const io = init.io;
-
- // Get the current working directory
- const cwd = std.Io.Dir.cwd();
-
- // try to open ./output assuming you did your 106_files exercise
- var output_dir = try cwd.openDir(io, "output", .{});
- defer output_dir.close(io);
-
- // try to open the file
- const file = try output_dir.openFile(io, "zigling.txt", .{});
- defer file.close(io);
-
- // initialize an array of u8 with all letter 'A'
- // we need to pick the size of the array, 64 seems like a good number
- // fix the initialization below
- var content = ['A']*64;
- // this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`
- std.debug.print("{s}\n", .{content});
-
- var file_reader = file.reader(io, &.{});
- const reader = &file_reader.interface;
-
- // okay, seems like a threat of violence is not the answer in this case
- // can you go here to find a way to read the content?
- // https://ziglang.org/documentation/master/std/#std.Io.Reader
- // hint: look for a method that reads into a slice
- const bytes_read = zig_read_the_file_or_i_will_fight_you(&content);
-
- // Woah, too screamy. I know you're excited for zigling time but tone it down a bit.
- // Can you print only what we read from the file?
- std.debug.print("Successfully Read {d} bytes: {s}\n", .{
- bytes_read,
- content, // change this line only
- });
-}
diff --git a/exercises/107_threading.zig b/exercises/107_threading.zig
new file mode 100644
index 0000000..3c3fa21
--- /dev/null
+++ b/exercises/107_threading.zig
@@ -0,0 +1,129 @@
+//
+// 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.
+//
+// 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
+//
+// 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.
+//
+// The following diagram roughly illustrates the difference between
+// the various types of process execution:
+//
+//
+// Synchronous Asynchronous
+// Processing Processing Multithreading
+// ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
+// │ Thread 1 │ │ Thread 1 │ │ Thread 1 │ │ Thread 2 │
+// ├──────────┤ ├──────────┤ ├──────────┤ ├──────────┤ Overall Time
+// └──┼┼┼┼┼───┴─┴──┼┼┼┼┼───┴──┴──┼┼┼┼┼───┴─┴──┼┼┼┼┼───┴──┬───────┬───────┬──
+// ├───┤ ├───┤ ├───┤ ├───┤ │ │ │
+// │ T │ │ T │ │ T │ │ T │ │ │ │
+// │ a │ │ a │ │ a │ │ a │ │ │ │
+// │ s │ │ s │ │ s │ │ s │ │ │ │
+// │ k │ │ k │ │ k │ │ k │ │ │ │
+// │ │ │ │ │ │ │ │ │ │ │
+// │ 1 │ │ 1 │ │ 1 │ │ 3 │ │ │ │
+// └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ │ │ │
+// │ │ │ │ 5 Sec │ │
+// ┌────┴───┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ │ │ │
+// │Blocking│ │ T │ │ T │ │ T │ │ │ │
+// └────┬───┘ │ a │ │ a │ │ a │ │ │ │
+// │ │ s │ │ s │ │ s │ │ 8 Sec │
+// ┌─┴─┐ │ k │ │ k │ │ k │ │ │ │
+// │ T │ │ │ │ │ │ │ │ │ │
+// │ a │ │ 2 │ │ 2 │ │ 4 │ │ │ │
+// │ s │ └─┬─┘ ├───┤ ├───┤ │ │ │
+// │ k │ │ │┼┼┼│ │┼┼┼│ ▼ │ 10 Sec
+// │ │ ┌─┴─┐ └───┴────────┴───┴───────── │ │
+// │ 1 │ │ T │ │ │
+// └─┬─┘ │ a │ │ │
+// │ │ s │ │ │
+// ┌─┴─┐ │ k │ │ │
+// │ T │ │ │ │ │
+// │ a │ │ 1 │ │ │
+// │ s │ ├───┤ │ │
+// │ k │ │┼┼┼│ ▼ │
+// │ │ └───┴──────────────────────────────────────────── │
+// │ 2 │ │
+// ├───┤ │
+// │┼┼┼│ ▼
+// └───┴────────────────────────────────────────────────────────────────
+//
+//
+// The diagram was modeled on the one in a blog in which the differences
+// between asynchronous processing and multithreading are explained in detail:
+// https://blog.devgenius.io/multi-threading-vs-asynchronous-programming-what-is-the-difference-3ebfe1179a5
+//
+// Our exercise is essentially about clarifying the approach in Zig and
+// therefore we try to keep it as simple as possible.
+// Multithreading in itself is already difficult enough. ;-)
+//
+const std = @import("std");
+
+pub fn main() !void {
+ // This is where the preparatory work takes place
+ // before the parallel processing begins.
+ std.debug.print("Starting work...\n", .{});
+
+ // These curly brackets are very important, they are necessary
+ // to enclose the area where the threads are called.
+ // Without these brackets, the program would not wait for the
+ // end of the threads and they would continue to run beyond the
+ // end of the program.
+ {
+ // Now we start the first thread, with the number as parameter
+ const handle = try std.Thread.spawn(.{}, thread_function, .{1});
+
+ // Waits for the thread to complete,
+ // then deallocates any resources created on `spawn()`.
+ defer handle.join();
+
+ // Second thread
+ const handle2 = try std.Thread.spawn(.{}, thread_function, .{-4}); // that can't be right?
+ defer handle2.join();
+
+ // Third thread
+ const handle3 = try std.Thread.spawn(.{}, thread_function, .{3});
+ defer ??? // <-- something is missing
+
+ // After the threads have been started,
+ // 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.fromMilliseconds(400), .awake);
+ std.debug.print("Some weird stuff, after starting the threads.\n", .{});
+ }
+ // After we have left the closed area, we wait until
+ // the threads have run through, if this has not yet been the case.
+ std.debug.print("Zig is cool!\n", .{});
+}
+
+// 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(id: usize) !void {
+ var io_instance: std.Io.Threaded = .init_single_threaded;
+ const io = io_instance.io();
+ 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 = 300 * ((5 - id % 3) - 2);
+ try io.sleep(std.Io.Duration.fromMilliseconds(@intCast(work_time)), .awake);
+
+ 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,
+// e.g. by setting up a pool and allowing the threads to communicate
+// with each other using semaphores.
+//
+// But that's a topic for another exercise.
diff --git a/exercises/108_labeled_switch.zig b/exercises/108_labeled_switch.zig
deleted file mode 100644
index 897fcf5..0000000
--- a/exercises/108_labeled_switch.zig
+++ /dev/null
@@ -1,79 +0,0 @@
-//
-// You've heard of while loops in exercises 011,012,013 and 014
-// You've also heard of switch expressions in exercises 030 and 31.
-// You've also seen how labels can be used in exercise 063.
-//
-// By combining while loops and switch statements with continue and break statements
-// one can create very concise State Machines.
-//
-// One such example would be:
-//
-// pub fn main() void {
-// var op: u8 = 1;
-// while (true) {
-// switch (op) {
-// 1 => { op = 2; continue; },
-// 2 => { op = 3; continue; },
-// 3 => return,
-// else => {},
-// }
-// break;
-// }
-// std.debug.print("This statement cannot be reached\n", .{});
-// }
-//
-// By combining all we've learned so far, we can now proceed with a labeled switch.
-//
-// A labeled switch is some extra syntactic sugar, which comes with all sorts of
-// candy (performance benefits). Don't believe me? Directly to source https://github.com/ziglang/zig/pull/21367
-//
-// Here is the previous excerpt implemented as a labeled switch instead:
-//
-// pub fn main() void {
-// foo: switch (@as(u8, 1)) {
-// 1 => continue :foo 2,
-// 2 => continue :foo 3,
-// 3 => return,
-// else => {},
-// }
-// std.debug.print("This statement cannot be reached\n", .{});
-// }
-//
-// The flow of execution on this second case is:
-// 1. The switch starts with value '1';
-// 2. The switch evaluates to case '1' which in turn uses the continue statement
-// to re-evaluate the labeled switch again, now providing the value '2';
-// 3. In the case '2' we repeat the same pattern as case '1'
-// but instead the value to be evaluated is now '3';
-// 4. Finally we get to case '3', where we return from the function as a whole,
-// so the debug statement is never executed.
-// 5. In this example, since the input does not have clear, exhaustive patterns and
-// can essentially be any 'u8' integer, we need to handle all cases not explicitly
-// covered by using the 'else => {}' branch as the default case.
-//
-//
-const std = @import("std");
-
-const PullRequestState = enum(u8) {
- Draft,
- InReview,
- Approved,
- Rejected,
- Merged,
-};
-
-pub fn main() void {
- // Oh no, your pull request keeps being rejected,
- // how would you fix it?
- pr: switch (PullRequestState.Draft) {
- PullRequestState.Draft => continue :pr PullRequestState.InReview,
- PullRequestState.InReview => continue :pr PullRequestState.Rejected,
- PullRequestState.Approved => continue :pr PullRequestState.Merged,
- PullRequestState.Rejected => {
- std.debug.print("The pull request has been rejected.\n", .{});
- return;
- },
- PullRequestState.Merged => break, // Would you know where to break to?
- }
- std.debug.print("The pull request has been merged.\n", .{});
-}
diff --git a/exercises/108_threading2.zig b/exercises/108_threading2.zig
new file mode 100644
index 0000000..374391a
--- /dev/null
+++ b/exercises/108_threading2.zig
@@ -0,0 +1,107 @@
+//
+// Now that we are familiar with the principles of multi-threading,
+// let's boldly venture into a practical example from mathematics.
+// We will determine the circle number PI with sufficient accuracy.
+//
+// There are different methods for this, and some of them are several
+// hundred years old. For us, the dusty procedures are surprisingly well
+// suited to our exercise. Because the mathematicians of the time didn't
+// have fancy computers with which we can calculate something like this
+// in seconds today.
+// Whereby, of course, it depends on the accuracy, i.e. how many digits
+// after the decimal point we are interested in.
+// But these old procedures can still be tackled with paper and pencil,
+// which is why they are easier for us to understand.
+// At least for me. ;-)
+//
+// So let's take a mental leap back a few years.
+// Around 1672 (if you want to know and read about it in detail, you can
+// do so on Wikipedia, for example), various mathematicians once again
+// discovered a method of approaching the circle number PI.
+// There were the Scottish mathematician Gregory and the German
+// mathematician Leibniz, and even a few hundred years earlier the Indian
+// mathematician Madhava. All of them independently developed the same
+// formula, which was published by Leibniz in 1682 in the journal
+// "Acta Eruditorum".
+// This is why this method has become known as the "Leibniz series",
+// although the other names are also often used today.
+// We will not go into the formula and its derivation in detail, but
+// will deal with the series straight away:
+//
+// 4 4 4 4 4
+// PI = --- - --- + --- - --- + --- ...
+// 1 3 5 7 9
+//
+// As you can clearly see, the series starts with the whole number 4 and
+// approaches the circle number by subtracting and adding smaller and
+// smaller parts of 4. Pretty much everyone has learned PI = 3.14 at school,
+// but very few people remember other digits, and this is rarely necessary
+// in practice. Because either you don't need the precision, or you use a
+// calculator in which the number is stored as a very precise constant.
+// But at some point this constant was calculated and we are doing the same
+// now. The question at this point is, how many partial values do we have
+// to calculate for which accuracy?
+//
+// The answer is chewing, to get 8 digits after the decimal point we need
+// 1,000,000,000 partial values. And for each additional digit we have to
+// add a zero.
+// Even fast computers - and I mean really fast computers - get a bit warmer
+// on the CPU when it comes to really many digits. But the 8 digits are
+// enough for us for now, because we want to understand the principle and
+// nothing more, right?
+//
+// As we have already discovered, the Leibniz series is a series with a
+// fixed distance of 2 between the individual partial values. This makes
+// it easy to apply a simple loop to it, because if we start with n = 1
+// (which is not necessarily useful now) we always have to add 2 in each
+// round.
+// But wait! The partial values are alternately added and subtracted.
+// This could also be achieved with one loop, but not very elegantly.
+// It also makes sense to split this between two CPUs, one calculates
+// the positive values and the other the negative values. And so we can
+// simply start two threads and add everything up at the end and we're
+// done.
+// We just have to remember that if only the positive or negative values
+// are calculated, the distances are twice as large, i.e. 4.
+//
+// So that the whole thing has a real learning effect, the first thread
+// call is specified and you have to make the second.
+// But don't worry, it will work out. :-)
+//
+const std = @import("std");
+
+pub fn main() !void {
+ const count = 1_000_000_000;
+ var pi_plus: f64 = 0;
+ var pi_minus: f64 = 0;
+
+ {
+ // First thread to calculate the plus numbers.
+ const handle1 = try std.Thread.spawn(.{}, thread_pi, .{ &pi_plus, 5, count });
+ defer handle1.join();
+
+ // Second thread to calculate the minus numbers.
+ ???
+
+ }
+ // Here we add up the results.
+ std.debug.print("PI ≈ {d:.8}\n", .{4 + pi_plus - pi_minus});
+}
+
+fn thread_pi(pi: *f64, begin: u64, end: u64) !void {
+ var n: u64 = begin;
+ while (n < end) : (n += 4) {
+ pi.* += 4 / @as(f64, @floatFromInt(n));
+ }
+}
+// If you wish, you can increase the number of loop passes, which
+// improves the number of digits.
+//
+// But be careful:
+// In order for parallel processing to really show its strengths,
+// the compiler must be given the "-O ReleaseFast" flag when it
+// is created. Otherwise the debug functions slow down the speed
+// to such an extent that seconds become minutes during execution.
+//
+// And you should remove the formatting restriction in "print",
+// otherwise you will not be able to see the additional digits.
diff --git a/exercises/109_files.zig b/exercises/109_files.zig
new file mode 100644
index 0000000..bf4de9b
--- /dev/null
+++ b/exercises/109_files.zig
@@ -0,0 +1,102 @@
+//
+// Until now, we've only been printing our output in the console,
+// which is good enough for fighting aliens and hermit bookkeeping.
+//
+// However, many other tasks require some interaction with the file system,
+// which is the underlying structure for organizing files on your computer.
+//
+// The file system provides a hierarchical structure for storing files
+// by organizing them into directories, which hold files and other directories,
+// thus creating a tree structure that can be navigated.
+//
+// Fortunately, the Zig Standard Library provides a simple API for interacting
+// with the file system, see the detail documentation here:
+//
+// https://ziglang.org/documentation/master/std/#std.Io
+//
+// In this exercise, we'll try to:
+// - create a new directory,
+// - open a file in the directory,
+// - write to the file.
+//
+// Note: For simplicity, we write byte-by-byte without buffering.
+// In real applications, you'd typically use a buffer for better
+// performance. We'll learn about buffered I/O in a later exercise.
+//
+const std = @import("std");
+
+pub fn main(init: std.process.Init) !void {
+ // default I/O implementation
+ const io = init.io;
+
+ // first we get the current working directory
+ const cwd: std.Io.Dir = std.Io.Dir.cwd();
+
+ // then we'll try to make a new directory /output/
+ // to store our output files.
+ cwd.createDir(io, "output", .default_dir) catch |e| switch (e) {
+ // there is a chance you might want to run this
+ // program more than once and the path might already
+ // have been created, so we'll have to handle this error
+ // by doing nothing
+ //
+ // we want to catch error.PathAlreadyExists and do nothing
+ ??? => {},
+ // if there's any other unexpected error we just propagate it through
+ else => return e,
+ };
+
+ // then we'll try to open our freshly created directory
+ // wait a minute...
+ // opening a directory might fail!
+ // what should we do here?
+ var output_dir: std.Io.Dir = try cwd.openDir(io, "output", .{});
+ defer output_dir.close(io);
+
+ // we try to open the file `zigling.txt`,
+ // and propagate any error up
+ const file: std.Io.File = try output_dir.createFile(io, "zigling.txt", .{});
+ // it is a good habit to close a file after you are done with it
+ // so that other programs can read it and prevent data corruption
+ // but here we are not yet done writing to the file
+ // if only there were a keyword in Zig that
+ // allowed you to "defer" code execution to the end of the scope...
+ file.close(io);
+
+ // you are not allowed to move these lines above the file closing line!
+ var file_writer = file.writer(io, &.{});
+ const writer = &file_writer.interface;
+
+ const byte_written = try writer.write("It's zigling time!");
+ std.debug.print("Successfully wrote {d} bytes.\n", .{byte_written});
+}
+// to check if you actually write to the file, you can either,
+// 1. open the file in your text editor, or
+// 2. print the content of the file in the console with one of these commands
+// Linux/macOS: >> cat ./output/zigling.txt
+// Windows (CMD): >> type .\output\zigling.txt
+//
+//
+// More on Creating files
+//
+// notice in:
+// ... try output_dir.createFile(io, "zigling.txt", .{});
+// ^^^
+// we passed this anonymous struct to the function call
+//
+// this is the struct `CreateFlag` with default fields
+// {
+// read: bool = false,
+// truncate: bool = true,
+// exclusive: bool = false,
+// lock: Lock = .none,
+// lock_nonblocking: bool = false,
+// mode: Mode = default_mode
+// }
+//
+// Question:
+// - what should you do if you want to also read the file after opening it?
+// - go to the documentation of the struct `std.Io.Dir` here:
+// https://ziglang.org/documentation/master/std/#std.Io.Dir
+// - can you find a function for opening a file? how about deleting a file?
+// - what kind of options can you use with those functions?
diff --git a/exercises/109_vectors.zig b/exercises/109_vectors.zig
deleted file mode 100644
index 96892ca..0000000
--- a/exercises/109_vectors.zig
+++ /dev/null
@@ -1,147 +0,0 @@
-// So far in Ziglings, we've seen how for loops can be used to
-// repeat calculations across an array in several ways.
-//
-// For loops are generally great for this kind of task, but
-// sometimes they don't fully utilize the capabilities of the
-// CPU.
-//
-// Most modern CPUs can execute instructions in which SEVERAL
-// calculations are performed WITHIN registers at the SAME TIME.
-// These are known as "single instruction, multiple data" (SIMD)
-// instructions. SIMD instructions can make code significantly
-// more performant.
-//
-// To see why, imagine we have a program in which we take the
-// square root of four (changing) f32 floats.
-//
-// A simple compiler would take the program and produce machine code
-// which calculates each square root sequentially. Most registers on
-// modern CPUs have 64 bits, so we could imagine that each float moves
-// into a 64-bit register, and the following happens four times:
-//
-// 32 bits 32 bits
-// +-------------------+
-// register | 0 | x |
-// +-------------------+
-//
-// |
-// [SQRT instruction]
-// V
-//
-// +-------------------+
-// | 0 | sqrt(x) |
-// +-------------------+
-//
-// Notice that half of the register contains blank data to which
-// nothing happened. What a waste! What if we were able to use
-// that space instead? This is the idea at the core of SIMD.
-//
-// Most modern CPUs contain specialized registers with at least 128 bits
-// for performing SIMD instructions. On a machine with 128-bit SIMD
-// registers, a smart compiler would probably NOT issue four sqrt
-// instructions as above, but instead pack the floats into a single
-// 128-bit register, then execute a single "packed" sqrt
-// instruction to do ALL the square root calculations at once.
-//
-// For example:
-//
-//
-// 32 bits 32 bits 32 bits 32 bits
-// +---------------------------------------+
-// register | 4.0 | 9.0 | 25.0 | 49.0 |
-// +---------------------------------------+
-//
-// |
-// [SIMD SQRT instruction]
-// V
-//
-// +---------------------------------------+
-// register | 2.0 | 3.0 | 5.0 | 7.0 |
-// +---------------------------------------+
-//
-// Pretty cool, right?
-//
-// Code with SIMD instructions is usually more performant than code
-// without SIMD instructions. Zig cares a lot about performance,
-// so it has built-in support for SIMD! It has a data structure that
-// directly supports SIMD instructions:
-//
-// +-----------+
-// | Vectors |
-// +-----------+
-//
-// Operations performed on vectors in Zig will be done in parallel using
-// SIMD instructions, whenever possible.
-//
-// Defining vectors in Zig is straightforwards. No library import is needed.
-const v1 = @Vector(3, i32){ 1, 10, 100 };
-const v2 = @Vector(3, f32){ 2.0, 3.0, 5.0 };
-
-// Vectors support the same builtin operators as their underlying base types.
-const v3 = v1 + v1; // { 2, 20, 200};
-const v4 = v2 * v2; // { 4.0, 9.0, 25.0};
-
-// Intrinsics that apply to base types usually extend to vectors.
-const v5: @Vector(3, f32) = @floatFromInt(v3); // { 2.0, 20.0, 200.0}
-const v6 = v4 - v5; // { 2.0, -11.0, -175.0}
-const v7 = @abs(v6); // { 2.0, 11.0, 175.0}
-
-// We can make constant vectors, and reduce vectors.
-const v8: @Vector(4, u8) = @splat(2); // { 2, 2, 2, 2}
-const v8_sum = @reduce(.Add, v8); // 8
-const v8_min = @reduce(.Min, v8); // 2
-
-// Fixed-length arrays can be automatically assigned to vectors (and vice-versa).
-const single_digit_primes = [4]i8{ 2, 3, 5, 7 };
-const prime_vector: @Vector(4, i8) = single_digit_primes;
-
-// Now let's use vectors to simplify and optimize some code!
-//
-// Ewa is writing a program in which they frequently want to compare
-// two lists of four f32s. Ewa expects the lists to be similar, and
-// wants to determine the largest pairwise difference between the lists.
-//
-// Ewa wrote the following function to figure this out.
-
-fn calcMaxPairwiseDiffOld(list1: [4]f32, list2: [4]f32) f32 {
- var max_diff: f32 = 0;
- for (list1, list2) |n1, n2| {
- const abs_diff = @abs(n1 - n2);
- if (abs_diff > max_diff) {
- max_diff = abs_diff;
- }
- }
- return max_diff;
-}
-
-// Ewa heard about vectors in Zig, and started writing a new vector
-// version of the function, but has got stuck!
-//
-// Help Ewa finish the vector version! The examples above should help.
-
-const Vec4 = @Vector(4, f32);
-fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 {
- const abs_diff_vec = ???;
- const max_diff = @reduce(???, abs_diff_vec);
- return max_diff;
-}
-
-// Quite the simplification! We could even write the function in one line
-// and it would still be readable.
-//
-// Since the entire function is now expressed in terms of vector operations,
-// the Zig compiler will easily be able to compile it down to machine code
-// which utilizes the all-powerful SIMD instructions and does a lot of the
-// computation in parallel.
-
-const std = @import("std");
-const print = std.debug.print;
-
-pub fn main() void {
- const l1 = [4]f32{ 3.141, 2.718, 0.577, 1.000 };
- const l2 = [4]f32{ 3.154, 2.707, 0.591, 0.993 };
- const mpd_old = calcMaxPairwiseDiffOld(l1, l2);
- const mpd_new = calcMaxPairwiseDiffNew(l1, l2);
- print("Max difference (old fn): {d: >5.3}\n", .{mpd_old});
- print("Max difference (new fn): {d: >5.3}\n", .{mpd_new});
-}
diff --git a/exercises/110_files2.zig b/exercises/110_files2.zig
new file mode 100644
index 0000000..c363086
--- /dev/null
+++ b/exercises/110_files2.zig
@@ -0,0 +1,61 @@
+//
+// Prerequisite :
+// - exercise/106_files.zig, or
+// - create a file {project_root}/output/zigling.txt
+// with content `It's zigling time!`(18 bytes total)
+//
+// Now there's no point in writing to a file if we don't read from it, am I right?
+// Let's write a program to read the content of the file that we just created.
+//
+// I am assuming that you've created the appropriate files for this to work.
+//
+// Alright, bud, lean in close. Here's the game plan.
+// - First, we open the {project_root}/output/ directory
+// - Secondly, we open file `zigling.txt` in that directory
+// - Then, we initialize an array of characters with all letter 'A', and print it
+// - After that, we read the content of the file into the array
+// - Finally, we print out the content we just read
+//
+// Note: For simplicity, we read byte-by-byte without buffering.
+// In real applications, you'd typically use a buffer for better
+// performance. We'll learn about buffered I/O in a later exercise.
+
+const std = @import("std");
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+
+ // Get the current working directory
+ const cwd = std.Io.Dir.cwd();
+
+ // try to open ./output assuming you did your 106_files exercise
+ var output_dir = try cwd.openDir(io, "output", .{});
+ defer output_dir.close(io);
+
+ // try to open the file
+ const file = try output_dir.openFile(io, "zigling.txt", .{});
+ defer file.close(io);
+
+ // initialize an array of u8 with all letter 'A'
+ // we need to pick the size of the array, 64 seems like a good number
+ // fix the initialization below
+ var content = ['A']*64;
+ // this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`
+ std.debug.print("{s}\n", .{content});
+
+ var file_reader = file.reader(io, &.{});
+ const reader = &file_reader.interface;
+
+ // okay, seems like a threat of violence is not the answer in this case
+ // can you go here to find a way to read the content?
+ // https://ziglang.org/documentation/master/std/#std.Io.Reader
+ // hint: look for a method that reads into a slice
+ const bytes_read = zig_read_the_file_or_i_will_fight_you(&content);
+
+ // Woah, too screamy. I know you're excited for zigling time but tone it down a bit.
+ // Can you print only what we read from the file?
+ std.debug.print("Successfully Read {d} bytes: {s}\n", .{
+ bytes_read,
+ content, // change this line only
+ });
+}
diff --git a/exercises/110_quiz9.zig b/exercises/110_quiz9.zig
deleted file mode 100644
index 8f5cb61..0000000
--- a/exercises/110_quiz9.zig
+++ /dev/null
@@ -1,484 +0,0 @@
-// ----------------------------------------------------------------------------
-// Quiz Time: Toggling, Setting, and Clearing Bits
-// ----------------------------------------------------------------------------
-//
-// Another exciting thing about Zig is its suitability for embedded
-// programming. Your Zig code doesn't have to remain on your laptop; you can
-// also deploy your code to microcontrollers! This means you can write Zig to
-// drive your next robot or greenhouse climate control system! Ready to enter
-// the exciting world of embedded programming? Let's get started!
-//
-// ----------------------------------------------------------------------------
-// Some Background
-// ----------------------------------------------------------------------------
-//
-// A common activity in microcontroller programming is setting and clearing
-// bits on input and output pins. This lets you control LEDs, sensors, motors
-// and more! In a previous exercise (097_bit_manipulation.zig) you learned how
-// to swap two bytes using the ^ (XOR - exclusive or) operator. This quiz will
-// test your knowledge of bit manipulation in Zig while giving you a taste of
-// what it's like to control registers in a real microcontroller. Included at
-// the end are some helper functions that demonstrate how we might make our
-// code a little more readable.
-//
-// Below is a pinout diagram for the famous ATmega328 AVR microcontroller used
-// as the primary microchip on popular microcontroller platforms like the
-// Arduino UNO.
-//
-// ============ PINOUT DIAGRAM FOR ATMEGA328 MICROCONTROLLER ============
-// _____ _____
-// | U |
-// (RESET) PC6 --| 1 28 |-- PC5
-// PD0 --| 2 27 |-- PC4
-// PD1 --| 3 26 |-- PC3
-// PD2 --| 4 25 |-- PC2
-// PD3 --| 5 24 |-- PC1
-// PD4 --| 6 23 |-- PC0
-// VCC --| 7 22 |-- GND
-// GND --| 8 21 |-- AREF
-// |-- PB6 --| 9 20 |-- AVCC
-// |-- PB7 --| 10 19 |-- PB5 --|
-// | PD5 --| 11 18 |-- PB4 --|
-// | PD6 --| 12 17 |-- PB3 --|
-// | PD7 --| 13 16 |-- PB2 --|
-// |-- PB0 --| 14 15 |-- PB1 --|
-// | |___________| |
-// \_______________________________/
-// |
-// PORTB
-//
-// Drawing inspiration from this diagram, we'll use the pins for PORTB as our
-// mental model for this quiz on bit manipulation. It should be noted that
-// in the following problems we are using ordinary variables, one of which we
-// have named PORTB, to simulate modifying the bits of real hardware registers.
-// But in actual microcontroller code, PORTB would be defined something like
-// this:
-// pub const PORTB = @as(*volatile u8, @ptrFromInt(0x25));
-//
-// This lets the compiler know not to make any optimizations to PORTB so that
-// the IO pins are properly mapped to our code.
-//
-// NOTE : To keep things simple, the following problems are given using type
-// u4, so applying the output to PORTB would only affect the lower four pins
-// PB0..PB3. Of course, there is nothing to prevent you from swapping the u4
-// with a u8 so you can control all 8 of PORTB's IO pins.
-
-const std = @import("std");
-const print = std.debug.print;
-const testing = std.testing;
-
-pub fn main() !void {
- var PORTB: u4 = 0b0000; // only 4 bits wide for simplicity
-
- // ------------------------------------------------------------------------
- // Quiz
- // ------------------------------------------------------------------------
-
- // See if you can solve the following problems. The last two problems throw
- // you a bit of a curve ball. Try solving them on your own. If you need
- // help, scroll to the bottom of main to see some in depth explanations on
- // toggling, setting, and clearing bits in Zig.
-
- print("Toggle pins with XOR on PORTB\n", .{});
- print("-----------------------------\n", .{});
- PORTB = 0b1100;
- print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
- print("^ {b:0>4} // (bitmask)\n", .{0b0101});
- PORTB ^= (1 << 1) | (1 << 0); // What's wrong here?
- checkAnswer(0b1001, PORTB);
-
- newline();
-
- PORTB = 0b1100;
- print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
- print("^ {b:0>4} // (bitmask)\n", .{0b0011});
- PORTB ^= (1 << 1) & (1 << 0); // What's wrong here?
- checkAnswer(0b1111, PORTB);
-
- newline();
-
- print("Set pins with OR on PORTB\n", .{});
- print("-------------------------\n", .{});
-
- PORTB = 0b1001; // reset PORTB
- print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
- print("| {b:0>4} // (bitmask)\n", .{0b0100});
- PORTB = PORTB ??? (1 << 2); // What's missing here?
- checkAnswer(0b1101, PORTB);
-
- newline();
-
- PORTB = 0b1001; // reset PORTB
- print(" {b:0>4} // (reset state)\n", .{PORTB});
- print("| {b:0>4} // (bitmask)\n", .{0b0100});
- PORTB ??? (1 << 2); // What's missing here?
- checkAnswer(0b1101, PORTB);
-
- newline();
-
- print("Clear pins with AND and NOT on PORTB\n", .{});
- print("------------------------------------\n", .{});
-
- PORTB = 0b1110; // reset PORTB
- print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
- print("& {b:0>4} // (bitmask)\n", .{0b1011});
- PORTB = PORTB & ???@as(u4, 1 << 2); // What character is missing here?
- checkAnswer(0b1010, PORTB);
-
- newline();
-
- PORTB = 0b0111; // reset PORTB
- print(" {b:0>4} // (reset state)\n", .{PORTB});
- print("& {b:0>4} // (bitmask)\n", .{0b1110});
- PORTB &= ~(1 << 0); // What's missing here?
- checkAnswer(0b0110, PORTB);
-
- newline();
- newline();
-}
-
-// ************************************************************************
-// IN-DEPTH EXPLANATIONS BELOW
-// ************************************************************************
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-// ------------------------------------------------------------------------
-// Toggling bits with XOR:
-// ------------------------------------------------------------------------
-// XOR stands for "exclusive or". We can toggle bits with the ^ (XOR)
-// bitwise operator, like so:
-//
-//
-// In order to output a 1, the logic of an XOR operation requires that the
-// two input bits are of different values. Therefore, 0 ^ 1 and 1 ^ 0 will
-// both yield a 1 but 0 ^ 0 and 1 ^ 1 will output 0. XOR's unique behavior
-// of outputting a 0 when both inputs are 1s is what makes it different from
-// the OR operator; it also gives us the ability to toggle bits by putting
-// 1s into our bitmask.
-//
-// - 1s in our bitmask operand, can be thought of as causing the
-// corresponding bits in the other operand to flip to the opposite value.
-// - 0s cause no change.
-//
-// The 0s in our bitmask preserve these values
-// -XOR op- ---expanded--- in the output.
-// _______________/
-// / /
-// 1100 1 1 0 0
-// ^ 0101 0 1 0 1 (bitmask)
-// ------ - - - -
-// = 1001 1 0 0 1 <- This bit was already cleared.
-// \_______\
-// \
-// We can think of these bits having flipped
-// because of the presence of 1s in those columns
-// of our bitmask.
-//
-// Now let's take a look at setting bits with the | operator.
-//
-//
-//
-//
-//
-// ------------------------------------------------------------------------
-// Setting bits with OR:
-// ------------------------------------------------------------------------
-// We can set bits on PORTB with the | (OR) operator, like so:
-//
-// var PORTB: u4 = 0b1001;
-// PORTB = PORTB | 0b0010;
-// print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011
-//
-// -OR op- ---expanded---
-// _ Set only this bit.
-// /
-// 1001 1 0 0 1
-// | 0010 0 0 1 0 (bitmask)
-// ------ - - - -
-// = 1011 1 0 1 1
-// \___\_______\
-// \
-// These bits remain untouched because OR-ing with
-// a 0 effects no change.
-//
-// ------------------------------------------------------------------------
-// To create a bitmask like 0b0010 used above:
-//
-// 1. First, shift the value 1 over one place with the bitwise << (shift
-// left) operator as indicated below:
-// 1 << 0 -> 0001
-// 1 << 1 -> 0010 <-- Shift 1 one place to the left
-// 1 << 2 -> 0100
-// 1 << 3 -> 1000
-//
-// This allows us to rewrite the above code like this:
-//
-// var PORTB: u4 = 0b1001;
-// PORTB = PORTB | (1 << 1);
-// print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011
-//
-// Finally, as in the C language, Zig allows us to use the |= operator, so
-// we can rewrite our code again in an even more compact and idiomatic
-// form: PORTB |= (1 << 1)
-
-// So now we've covered how to toggle and set bits. What about clearing
-// them? Well, this is where Zig throws us a curve ball. Don't worry we'll
-// go through it step by step.
-//
-//
-//
-//
-//
-// ------------------------------------------------------------------------
-// Clearing bits with AND and NOT:
-// ------------------------------------------------------------------------
-// We can clear bits with the & (AND) bitwise operator, like so:
-
-// PORTB = 0b1110; // reset PORTB
-// PORTB = PORTB & 0b1011;
-// print("PORTB: {b:0>4}\n", .{PORTB}); // output -> 1010
-//
-// - 0s clear bits when used in conjunction with a bitwise AND.
-// - 1s do nothing, thus preserving the original bits.
-//
-// -AND op- ---expanded---
-// __________ Clear only this bit.
-// /
-// 1110 1 1 1 0
-// & 1011 1 0 1 1 (bitmask)
-// ------ - - - -
-// = 1010 1 0 1 0 <- This bit was already cleared.
-// \_______\
-// \
-// These bits remain untouched because AND-ing with a
-// 1 preserves the original bit value whether 0 or 1.
-//
-// ------------------------------------------------------------------------
-// We can use the ~ (NOT) operator to easily create a bitmask like 1011:
-//
-// 1. First, shift the value 1 over two places with the bit-wise << (shift
-// left) operator as indicated below:
-// 1 << 0 -> 0001
-// 1 << 1 -> 0010
-// 1 << 2 -> 0100 <- The 1 has been shifted two places to the left
-// 1 << 3 -> 1000
-//
-// 2. The second step in creating our bitmask is to invert the bits
-// ~0100 -> 1011
-// in C we would write this as:
-// ~(1 << 2) -> 1011
-//
-// But if we try to compile ~(1 << 2) in Zig, we'll get an error:
-// unable to perform binary not operation on type 'comptime_int'
-//
-// Before Zig can invert our bits, it needs to know the number of
-// bits it's being asked to invert.
-//
-// We do this with the @as (cast as) built-in like this:
-// @as(u4, 1 << 2) -> 0100
-//
-// Finally, we can invert our new mask by placing the NOT ~ operator
-// before our expression, like this:
-// ~@as(u4, 1 << 2) -> 1011
-//
-// If you are offput by the fact that you can't simply invert bits like
-// you can in languages such as C without casting to a particular size
-// of integer, you're not alone. However, this is actually another
-// instance where Zig is really helpful because it protects you from
-// difficult to debug integer overflow bugs that can have you tearing
-// your hair out. In the interest of keeping things sane, Zig requires
-// you simply to tell it the size of number you are inverting. In the
-// words of Andrew Kelley, "If you want to invert the bits of an
-// integer, zig has to know how many bits there are."
-//
-// For more insight into the Zig team's position on why the language
-// takes the approach it does with the ~ operator, take a look at
-// Andrew's comments on the following github issue:
-// https://github.com/ziglang/zig/issues/1382#issuecomment-414459529
-//
-// Whew, so after all that what we end up with is:
-// PORTB = PORTB & ~@as(u4, 1 << 2);
-//
-// We can shorten this with the &= combined AND and assignment operator,
-// which applies the AND operator on PORTB and then reassigns PORTB. Here's
-// what that looks like:
-// PORTB &= ~@as(u4, 1 << 2);
-//
-
-// ------------------------------------------------------------------------
-// Conclusion
-// ------------------------------------------------------------------------
-//
-// While the examples in this quiz have used only 4-bit wide variables,
-// working with 8 bits is no different. Here's an example where we set
-// every other bit beginning with the two's place:
-
-// var PORTD: u8 = 0b0000_0000;
-// print("PORTD: {b:0>8}\n", .{PORTD});
-// PORTD |= (1 << 1);
-// PORTD = setBit(u8, PORTD, 3);
-// PORTD |= (1 << 5) | (1 << 7);
-// print("PORTD: {b:0>8} // set every other bit\n", .{PORTD});
-// PORTD = ~PORTD;
-// print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD});
-// newline();
-//
-// // Here we clear every other bit beginning with the two's place.
-//
-// PORTD = 0b1111_1111;
-// print("PORTD: {b:0>8}\n", .{PORTD});
-// PORTD &= ~@as(u8, 1 << 1);
-// PORTD = clearBit(u8, PORTD, 3);
-// PORTD &= ~@as(u8, (1 << 5) | (1 << 7));
-// print("PORTD: {b:0>8} // clear every other bit\n", .{PORTD});
-// PORTD = ~PORTD;
-// print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD});
-// newline();
-
-// ----------------------------------------------------------------------------
-// Here are some helper functions for manipulating bits
-// ----------------------------------------------------------------------------
-
-// Functions for setting, clearing, and toggling a single bit
-fn setBit(comptime T: type, byte: T, comptime bit_pos: T) !T {
- return byte | (1 << bit_pos);
-}
-
-test "setBit" {
- try testing.expectEqual(setBit(u8, 0b0000_0000, 3), 0b0000_1000);
-}
-
-fn clearBit(comptime T: type, byte: T, comptime bit_pos: T) T {
- return byte & ~@as(T, (1 << bit_pos));
-}
-
-test "clearBit" {
- try testing.expectEqual(clearBit(u8, 0b1111_1111, 0), 0b1111_1110);
-}
-
-fn toggleBit(comptime T: type, byte: T, comptime bit_pos: T) T {
- return byte ^ (1 << bit_pos);
-}
-
-test "toggleBit" {
- var byte = toggleBit(u8, 0b0000_0000, 0);
- try testing.expectEqual(byte, 0b0000_0001);
- byte = toggleBit(u8, byte, 0);
- try testing.expectEqual(byte, 0b0000_0000);
-}
-
-// ----------------------------------------------------------------------------
-// Some additional functions for setting, clearing, and toggling multiple bits
-// at once with a tuple because, hey, why not?
-// ----------------------------------------------------------------------------
-//
-
-fn createBitmask(comptime T: type, comptime bits: anytype) !T {
- comptime var bitmask: T = 0;
- inline for (bits) |bit| {
- if (bit >= @bitSizeOf(T)) return error.BitPosTooLarge;
- if (bit < 0) return error.BitPosTooSmall;
-
- bitmask |= (1 << bit);
- }
- return bitmask;
-}
-
-test "creating bitmasks from a tuple" {
- try testing.expectEqual(createBitmask(u8, .{0}), 0b0000_0001);
- try testing.expectEqual(createBitmask(u8, .{1}), 0b0000_0010);
- try testing.expectEqual(createBitmask(u8, .{2}), 0b0000_0100);
- try testing.expectEqual(createBitmask(u8, .{3}), 0b0000_1000);
- //
- try testing.expectEqual(createBitmask(u8, .{ 0, 4 }), 0b0001_0001);
- try testing.expectEqual(createBitmask(u8, .{ 1, 5 }), 0b0010_0010);
- try testing.expectEqual(createBitmask(u8, .{ 2, 6 }), 0b0100_0100);
- try testing.expectEqual(createBitmask(u8, .{ 3, 7 }), 0b1000_1000);
-
- try testing.expectError(error.BitPosTooLarge, createBitmask(u4, .{4}));
-}
-
-fn setBits(byte: u8, bits: anytype) !u8 {
- const bitmask = try createBitmask(u8, bits);
- return byte | bitmask;
-}
-
-test "setBits" {
- try testing.expectEqual(setBits(0b0000_0000, .{0}), 0b0000_0001);
- try testing.expectEqual(setBits(0b0000_0000, .{7}), 0b1000_0000);
-
- try testing.expectEqual(setBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);
- try testing.expectEqual(setBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);
-
- try testing.expectEqual(setBits(0b0000_0000, .{ 2, 3, 4, 5 }), 0b0011_1100);
-
- try testing.expectError(error.BitPosTooLarge, setBits(0b1111_1111, .{8}));
- try testing.expectError(error.BitPosTooSmall, setBits(0b1111_1111, .{-1}));
-}
-
-fn clearBits(comptime byte: u8, comptime bits: anytype) !u8 {
- const bitmask: u8 = try createBitmask(u8, bits);
- return byte & ~@as(u8, bitmask);
-}
-
-test "clearBits" {
- try testing.expectEqual(clearBits(0b1111_1111, .{0}), 0b1111_1110);
- try testing.expectEqual(clearBits(0b1111_1111, .{7}), 0b0111_1111);
-
- try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
- try testing.expectEqual(clearBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
-
- try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 6, 7 }), 0b0011_1100);
-
- try testing.expectError(error.BitPosTooLarge, clearBits(0b1111_1111, .{8}));
- try testing.expectError(error.BitPosTooSmall, clearBits(0b1111_1111, .{-1}));
-}
-
-fn toggleBits(comptime byte: u8, comptime bits: anytype) !u8 {
- const bitmask = try createBitmask(u8, bits);
- return byte ^ bitmask;
-}
-
-test "toggleBits" {
- try testing.expectEqual(toggleBits(0b0000_0000, .{0}), 0b0000_0001);
- try testing.expectEqual(toggleBits(0b0000_0000, .{7}), 0b1000_0000);
-
- try testing.expectEqual(toggleBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
- try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);
-
- try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_0000);
- try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3 }), 0b0000_0000);
-
- try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 2, 4, 6 }), 0b0101_0101);
-
- try testing.expectError(error.BitPosTooLarge, toggleBits(0b1111_1111, .{8}));
- try testing.expectError(error.BitPosTooSmall, toggleBits(0b1111_1111, .{-1}));
-}
-
-// ----------------------------------------------------------------------------
-// Utility functions
-// ----------------------------------------------------------------------------
-
-fn newline() void {
- print("\n", .{});
-}
-
-fn checkAnswer(expected: u4, answer: u4) void {
- if (expected != answer) {
- print("*************************************************************\n", .{});
- print("= {b:0>4} <- INCORRECT! THE EXPECTED OUTPUT IS {b:0>4}\n", .{ answer, expected });
- print("*************************************************************\n", .{});
- } else {
- print("= {b:0>4}", .{answer});
- }
- newline();
-}
diff --git a/exercises/111_labeled_switch.zig b/exercises/111_labeled_switch.zig
new file mode 100644
index 0000000..897fcf5
--- /dev/null
+++ b/exercises/111_labeled_switch.zig
@@ -0,0 +1,79 @@
+//
+// You've heard of while loops in exercises 011,012,013 and 014
+// You've also heard of switch expressions in exercises 030 and 31.
+// You've also seen how labels can be used in exercise 063.
+//
+// By combining while loops and switch statements with continue and break statements
+// one can create very concise State Machines.
+//
+// One such example would be:
+//
+// pub fn main() void {
+// var op: u8 = 1;
+// while (true) {
+// switch (op) {
+// 1 => { op = 2; continue; },
+// 2 => { op = 3; continue; },
+// 3 => return,
+// else => {},
+// }
+// break;
+// }
+// std.debug.print("This statement cannot be reached\n", .{});
+// }
+//
+// By combining all we've learned so far, we can now proceed with a labeled switch.
+//
+// A labeled switch is some extra syntactic sugar, which comes with all sorts of
+// candy (performance benefits). Don't believe me? Directly to source https://github.com/ziglang/zig/pull/21367
+//
+// Here is the previous excerpt implemented as a labeled switch instead:
+//
+// pub fn main() void {
+// foo: switch (@as(u8, 1)) {
+// 1 => continue :foo 2,
+// 2 => continue :foo 3,
+// 3 => return,
+// else => {},
+// }
+// std.debug.print("This statement cannot be reached\n", .{});
+// }
+//
+// The flow of execution on this second case is:
+// 1. The switch starts with value '1';
+// 2. The switch evaluates to case '1' which in turn uses the continue statement
+// to re-evaluate the labeled switch again, now providing the value '2';
+// 3. In the case '2' we repeat the same pattern as case '1'
+// but instead the value to be evaluated is now '3';
+// 4. Finally we get to case '3', where we return from the function as a whole,
+// so the debug statement is never executed.
+// 5. In this example, since the input does not have clear, exhaustive patterns and
+// can essentially be any 'u8' integer, we need to handle all cases not explicitly
+// covered by using the 'else => {}' branch as the default case.
+//
+//
+const std = @import("std");
+
+const PullRequestState = enum(u8) {
+ Draft,
+ InReview,
+ Approved,
+ Rejected,
+ Merged,
+};
+
+pub fn main() void {
+ // Oh no, your pull request keeps being rejected,
+ // how would you fix it?
+ pr: switch (PullRequestState.Draft) {
+ PullRequestState.Draft => continue :pr PullRequestState.InReview,
+ PullRequestState.InReview => continue :pr PullRequestState.Rejected,
+ PullRequestState.Approved => continue :pr PullRequestState.Merged,
+ PullRequestState.Rejected => {
+ std.debug.print("The pull request has been rejected.\n", .{});
+ return;
+ },
+ PullRequestState.Merged => break, // Would you know where to break to?
+ }
+ std.debug.print("The pull request has been merged.\n", .{});
+}
diff --git a/exercises/111_packed.zig b/exercises/111_packed.zig
deleted file mode 100644
index 1058f76..0000000
--- a/exercises/111_packed.zig
+++ /dev/null
@@ -1,175 +0,0 @@
-//
-// We've already learned plenty about bit manipulation using bitwise operations
-// in exercices 097 and 098 and in quiz 110. The techniques we already know work
-// just fine, but creating masks and shifting individual bits around can become
-// quite tedious and unwieldy pretty quickly.
-// What if there was a better, a more convenient way to control invidivual bits?
-//
-// Luckily, Zig has a keyword for exactly this purpose:
-//
-// packed
-//
-// It doesn't do anything on its own, to unlock its potential (and to get our
-// program to compile) we have to attach it either to a struct or to a union
-// declaration:
-//
-// const Foo = packed struct { ... };
-// const Bar = packed union { ... };
-//
-// Now, what does this keyword even do?
-// To answer this question we first have to talk about *container layouts*.
-//
-// Plain structs and unions use the `auto` layout; it gives no guarantees about
-// their size or the order of the fields they contain, both are fully up to the
-// compiler (though both size and field order *are* guaranteed to be the same
-// across any single compilation unit).
-//
-// Attaching the `packed` keyword to a container makes it use `packed` layout:
-// Suddenly, all of its fields are *packed* together tightly without any padding
-// in between and their order is guaranteed to be the same as the one specified
-// in our source code. For structs, the size of the container is guaranteed to
-// be the sum of the (bit-)sizes of all of its fields. For unions, all fields
-// have to have the exact same (bit-)size (no padding allowed!); the union itself
-// is also guaranteed to be exactly of this size.
-//
-// If you're familiar with C, you might have already heard of structure packing
-// in a different context: arranging fields in a way that minimizes the amount
-// of alignment padding between them (or having the compiler do it for you).
-// This is *not* what Zig's `packed` keyword is for!
-//
-// Try to make the comptime assertions below pass:
-
-const PackedStruct = packed struct {
- a: u2,
- b: u?,
-};
-
-comptime {
- assert(@bitSizeOf(PackedStruct) == 6);
-}
-
-const PackedUnion = packed union {
- a: bool,
- b: u?,
-};
-
-comptime {
- assert(@bitSizeOf(PackedUnion) == 1);
-}
-
-// Now, how can we use this new knowledge to manipulate some bits?
-//
-// As you might have already guessed, `packed` containers are very useful for
-// representing bitflags or other tightly packed collections of bit-sized values
-// often found in file headers and network protocols.
-//
-// Let's take a look at a real-life example:
-// The LZ4 compression format (†) specifies a frame format to describe compressed
-// data. Each LZ4 frame has a descriptor, and each descriptor contains a 'FLG'
-// byte that specifies the contents of its frame:
-
-/// | BitNb | 7-6 | 5 | 4 | 3 | 2 | 1 | 0 |
-/// | ------- |-------|-------|----------|------|----------|--------|------|
-/// |FieldName|Version|B.Indep|B.Checksum|C.Size|C.Checksum|Reserved|DictID|
-///
-const FLG = packed struct(u8) {
- dict_id: bool,
- reserved: u1 = 0,
- content_checksum: bool,
- content_size: bool,
- block_checksum: bool,
- block_indepencence: bool,
- version: u2,
-};
-
-// Wait, what's with the `(u8)` after the `struct` keyword? What do integers have
-// to do with all of this?
-// Well, this is a good opportunity to come clear about something:
-// packed structs and packed unions aren't actually structs or unions at all...
-// They are merely integers in disguise! For all intents and purposes, their
-// fields are just convenient names for ranges of their underlying bits. To make
-// it easier to enforce size requirements for packed containers, Zig allows us
-// to specify a *backing integer* for them, just like for enums.
-//
-// In the case of `FLG`, we want our struct to occupy exactly a single byte, so
-// we specify `u8` as the backing integer. It's safe to convert between a packed
-// container and its backing integer using the builtin `@bitCast`.
-// The LZ4 spec also mandates that reserved bits must always be zero, so it's
-// good practice to set `0` as a default value for `reserved`.
-//
-// The fields of a packed struct start at the least significant bit of its backing
-// integer and end at its most significant bit. This is the case no matter what
-// endianness our target has.
-//
-// Try to silence the complaints below:
-
-const Bits = packed struct(u4) {
- a: u1 = 0,
- b: u1 = 0,
- c: u1 = 0,
- d: u1 = 0,
-};
-
-pub fn main() void {
- {
- const expected: Bits = @bitCast(@as(u4, 0b1000));
- const my_bits: Bits = .{};
- if (my_bits != expected) complain(my_bits, expected, @src());
- }
-
- {
- const expected: Bits = @bitCast(@as(u4, 0b0001));
- const my_bits: Bits = .{};
- if (my_bits != expected) complain(my_bits, expected, @src());
- }
-
- {
- const expected: Bits = @bitCast(@as(u4, 0b0010));
- const my_bits: Bits = .{};
- if (my_bits != expected) complain(my_bits, expected, @src());
- }
-
- {
- const expected: Bits = @bitCast(@as(u4, 0b0011));
- const my_bits: Bits = .{};
- if (my_bits != expected) complain(my_bits, expected, @src());
- }
-
- {
- const expected: Bits = @bitCast(@as(u4, 0b1101));
- const my_bits: Bits = .{};
- if (my_bits != expected) complain(my_bits, expected, @src());
- }
-}
-
-// As we can see, equality comparisons (`==` and `!=`) work for packed structs.
-// They also work for packed unions. However, since packed containers are not
-// naturally ordered, we can't use any other comparison operators on them.
-//
-// It's also possible to use packed containers in `switch` statements, which we
-// will cover in the next exercise!
-//
-// Since packed containers make very strong guarantees about their memory layout,
-// only a handful of types are eligible to be part of them.
-// The following types are allowed as field types:
-//
-// - integers
-// - floats
-// - bool
-// - void
-// - enums with explicit backing integers
-// - packed unions
-// - packed structs
-//
-
-const std = @import("std");
-const assert = std.debug.assert;
-
-fn complain(my_bits: Bits, expected: Bits, src_loc: std.builtin.SourceLocation) void {
- std.debug.print(
- "That's not quite right! You've got 0b{b:0>4}, but we want 0b{b:0>4} in line {d}.\n",
- .{ @as(u4, @bitCast(my_bits)), @as(u4, @bitCast(expected)), src_loc.line },
- );
-}
-
-// (†) https://github.com/lz4/lz4/blob/5c4c1fb2354133e1f3b087a341576985f8114bd5/doc/lz4_Frame_format.md#frame-descriptor
diff --git a/exercises/112_packed2.zig b/exercises/112_packed2.zig
deleted file mode 100644
index 2b6b558..0000000
--- a/exercises/112_packed2.zig
+++ /dev/null
@@ -1,78 +0,0 @@
-//
-// We've already learned about switch statements in exercises 030, 031 and 108.
-// They also work with packed containers:
-
-const S = packed struct(u2) {
- a: bool,
- b: i1,
-};
-
-// Try to make it compile without adding an `else` prong!
-
-comptime {
- const s: S = .{ .a = true, .b = -1 };
- switch (s) {
- .{ .a = true, .b = -1 } => {}, // ok!
- .{ .a = true, .b = ??? },
- .{ .a = ???, .b = 0 },
- .{ .a = ???, .b = ??? },
- => @compileError("We don't want to end up here!"),
- }
-}
-
-// As we can see, switching on packed structs is pretty straightforward.
-// When switching on packed unions however, we'll realize that a packed
-// union never keeps track of its active tag, not even in debug mode! This
-// means that packed unions compare solely by their bit pattern (again, just
-// like integers).
-
-const U = packed union(u2) {
- a: u2,
- b: i2,
-};
-
-// Find and remove the duplicate case!
-
-comptime {
- const u: U = .{ .a = 3 };
- switch (u) {
- .{ .a = 3 } => {}, // ok!
- .{ .a = 2 },
- .{ .b = 1 },
- .{ .b = -1 },
- .{ .a = 0 },
- => @compileError("We don't want to end up here!"),
- }
-}
-
-// Since packed unions don't have the concept of an active tag, it's always legal
-// to access any of their fields. This can be useful to view the same data from
-// different perspectives seamlessly.
-//
-// Try to make the float below negative:
-
-/// IEEE 754 half precision float
-const Float = packed union(u16) {
- value: f16,
- bits: packed struct(u16) {
- mantissa: u10,
- exponent: u5,
- sign: u1,
- },
-};
-
-pub fn main() void {
- // Reminder: if the sign bit of a float is set, the number is negative!
-
- var number: Float = .{ .value = 2.34 };
- number.bits.??? = ???;
- if (number.value != -2.34) {
- std.debug.print("Make it negative!\n", .{});
- }
-}
-
-// This concludes our introduction to packed containers. The next time you need
-// control over individual bits, keep them in mind as a potent alternative!
-//
-
-const std = @import("std");
diff --git a/exercises/112_vectors.zig b/exercises/112_vectors.zig
new file mode 100644
index 0000000..96892ca
--- /dev/null
+++ b/exercises/112_vectors.zig
@@ -0,0 +1,147 @@
+// So far in Ziglings, we've seen how for loops can be used to
+// repeat calculations across an array in several ways.
+//
+// For loops are generally great for this kind of task, but
+// sometimes they don't fully utilize the capabilities of the
+// CPU.
+//
+// Most modern CPUs can execute instructions in which SEVERAL
+// calculations are performed WITHIN registers at the SAME TIME.
+// These are known as "single instruction, multiple data" (SIMD)
+// instructions. SIMD instructions can make code significantly
+// more performant.
+//
+// To see why, imagine we have a program in which we take the
+// square root of four (changing) f32 floats.
+//
+// A simple compiler would take the program and produce machine code
+// which calculates each square root sequentially. Most registers on
+// modern CPUs have 64 bits, so we could imagine that each float moves
+// into a 64-bit register, and the following happens four times:
+//
+// 32 bits 32 bits
+// +-------------------+
+// register | 0 | x |
+// +-------------------+
+//
+// |
+// [SQRT instruction]
+// V
+//
+// +-------------------+
+// | 0 | sqrt(x) |
+// +-------------------+
+//
+// Notice that half of the register contains blank data to which
+// nothing happened. What a waste! What if we were able to use
+// that space instead? This is the idea at the core of SIMD.
+//
+// Most modern CPUs contain specialized registers with at least 128 bits
+// for performing SIMD instructions. On a machine with 128-bit SIMD
+// registers, a smart compiler would probably NOT issue four sqrt
+// instructions as above, but instead pack the floats into a single
+// 128-bit register, then execute a single "packed" sqrt
+// instruction to do ALL the square root calculations at once.
+//
+// For example:
+//
+//
+// 32 bits 32 bits 32 bits 32 bits
+// +---------------------------------------+
+// register | 4.0 | 9.0 | 25.0 | 49.0 |
+// +---------------------------------------+
+//
+// |
+// [SIMD SQRT instruction]
+// V
+//
+// +---------------------------------------+
+// register | 2.0 | 3.0 | 5.0 | 7.0 |
+// +---------------------------------------+
+//
+// Pretty cool, right?
+//
+// Code with SIMD instructions is usually more performant than code
+// without SIMD instructions. Zig cares a lot about performance,
+// so it has built-in support for SIMD! It has a data structure that
+// directly supports SIMD instructions:
+//
+// +-----------+
+// | Vectors |
+// +-----------+
+//
+// Operations performed on vectors in Zig will be done in parallel using
+// SIMD instructions, whenever possible.
+//
+// Defining vectors in Zig is straightforwards. No library import is needed.
+const v1 = @Vector(3, i32){ 1, 10, 100 };
+const v2 = @Vector(3, f32){ 2.0, 3.0, 5.0 };
+
+// Vectors support the same builtin operators as their underlying base types.
+const v3 = v1 + v1; // { 2, 20, 200};
+const v4 = v2 * v2; // { 4.0, 9.0, 25.0};
+
+// Intrinsics that apply to base types usually extend to vectors.
+const v5: @Vector(3, f32) = @floatFromInt(v3); // { 2.0, 20.0, 200.0}
+const v6 = v4 - v5; // { 2.0, -11.0, -175.0}
+const v7 = @abs(v6); // { 2.0, 11.0, 175.0}
+
+// We can make constant vectors, and reduce vectors.
+const v8: @Vector(4, u8) = @splat(2); // { 2, 2, 2, 2}
+const v8_sum = @reduce(.Add, v8); // 8
+const v8_min = @reduce(.Min, v8); // 2
+
+// Fixed-length arrays can be automatically assigned to vectors (and vice-versa).
+const single_digit_primes = [4]i8{ 2, 3, 5, 7 };
+const prime_vector: @Vector(4, i8) = single_digit_primes;
+
+// Now let's use vectors to simplify and optimize some code!
+//
+// Ewa is writing a program in which they frequently want to compare
+// two lists of four f32s. Ewa expects the lists to be similar, and
+// wants to determine the largest pairwise difference between the lists.
+//
+// Ewa wrote the following function to figure this out.
+
+fn calcMaxPairwiseDiffOld(list1: [4]f32, list2: [4]f32) f32 {
+ var max_diff: f32 = 0;
+ for (list1, list2) |n1, n2| {
+ const abs_diff = @abs(n1 - n2);
+ if (abs_diff > max_diff) {
+ max_diff = abs_diff;
+ }
+ }
+ return max_diff;
+}
+
+// Ewa heard about vectors in Zig, and started writing a new vector
+// version of the function, but has got stuck!
+//
+// Help Ewa finish the vector version! The examples above should help.
+
+const Vec4 = @Vector(4, f32);
+fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 {
+ const abs_diff_vec = ???;
+ const max_diff = @reduce(???, abs_diff_vec);
+ return max_diff;
+}
+
+// Quite the simplification! We could even write the function in one line
+// and it would still be readable.
+//
+// Since the entire function is now expressed in terms of vector operations,
+// the Zig compiler will easily be able to compile it down to machine code
+// which utilizes the all-powerful SIMD instructions and does a lot of the
+// computation in parallel.
+
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn main() void {
+ const l1 = [4]f32{ 3.141, 2.718, 0.577, 1.000 };
+ const l2 = [4]f32{ 3.154, 2.707, 0.591, 0.993 };
+ const mpd_old = calcMaxPairwiseDiffOld(l1, l2);
+ const mpd_new = calcMaxPairwiseDiffNew(l1, l2);
+ print("Max difference (old fn): {d: >5.3}\n", .{mpd_old});
+ print("Max difference (new fn): {d: >5.3}\n", .{mpd_new});
+}
diff --git a/exercises/113_quiz9.zig b/exercises/113_quiz9.zig
new file mode 100644
index 0000000..8f5cb61
--- /dev/null
+++ b/exercises/113_quiz9.zig
@@ -0,0 +1,484 @@
+// ----------------------------------------------------------------------------
+// Quiz Time: Toggling, Setting, and Clearing Bits
+// ----------------------------------------------------------------------------
+//
+// Another exciting thing about Zig is its suitability for embedded
+// programming. Your Zig code doesn't have to remain on your laptop; you can
+// also deploy your code to microcontrollers! This means you can write Zig to
+// drive your next robot or greenhouse climate control system! Ready to enter
+// the exciting world of embedded programming? Let's get started!
+//
+// ----------------------------------------------------------------------------
+// Some Background
+// ----------------------------------------------------------------------------
+//
+// A common activity in microcontroller programming is setting and clearing
+// bits on input and output pins. This lets you control LEDs, sensors, motors
+// and more! In a previous exercise (097_bit_manipulation.zig) you learned how
+// to swap two bytes using the ^ (XOR - exclusive or) operator. This quiz will
+// test your knowledge of bit manipulation in Zig while giving you a taste of
+// what it's like to control registers in a real microcontroller. Included at
+// the end are some helper functions that demonstrate how we might make our
+// code a little more readable.
+//
+// Below is a pinout diagram for the famous ATmega328 AVR microcontroller used
+// as the primary microchip on popular microcontroller platforms like the
+// Arduino UNO.
+//
+// ============ PINOUT DIAGRAM FOR ATMEGA328 MICROCONTROLLER ============
+// _____ _____
+// | U |
+// (RESET) PC6 --| 1 28 |-- PC5
+// PD0 --| 2 27 |-- PC4
+// PD1 --| 3 26 |-- PC3
+// PD2 --| 4 25 |-- PC2
+// PD3 --| 5 24 |-- PC1
+// PD4 --| 6 23 |-- PC0
+// VCC --| 7 22 |-- GND
+// GND --| 8 21 |-- AREF
+// |-- PB6 --| 9 20 |-- AVCC
+// |-- PB7 --| 10 19 |-- PB5 --|
+// | PD5 --| 11 18 |-- PB4 --|
+// | PD6 --| 12 17 |-- PB3 --|
+// | PD7 --| 13 16 |-- PB2 --|
+// |-- PB0 --| 14 15 |-- PB1 --|
+// | |___________| |
+// \_______________________________/
+// |
+// PORTB
+//
+// Drawing inspiration from this diagram, we'll use the pins for PORTB as our
+// mental model for this quiz on bit manipulation. It should be noted that
+// in the following problems we are using ordinary variables, one of which we
+// have named PORTB, to simulate modifying the bits of real hardware registers.
+// But in actual microcontroller code, PORTB would be defined something like
+// this:
+// pub const PORTB = @as(*volatile u8, @ptrFromInt(0x25));
+//
+// This lets the compiler know not to make any optimizations to PORTB so that
+// the IO pins are properly mapped to our code.
+//
+// NOTE : To keep things simple, the following problems are given using type
+// u4, so applying the output to PORTB would only affect the lower four pins
+// PB0..PB3. Of course, there is nothing to prevent you from swapping the u4
+// with a u8 so you can control all 8 of PORTB's IO pins.
+
+const std = @import("std");
+const print = std.debug.print;
+const testing = std.testing;
+
+pub fn main() !void {
+ var PORTB: u4 = 0b0000; // only 4 bits wide for simplicity
+
+ // ------------------------------------------------------------------------
+ // Quiz
+ // ------------------------------------------------------------------------
+
+ // See if you can solve the following problems. The last two problems throw
+ // you a bit of a curve ball. Try solving them on your own. If you need
+ // help, scroll to the bottom of main to see some in depth explanations on
+ // toggling, setting, and clearing bits in Zig.
+
+ print("Toggle pins with XOR on PORTB\n", .{});
+ print("-----------------------------\n", .{});
+ PORTB = 0b1100;
+ print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
+ print("^ {b:0>4} // (bitmask)\n", .{0b0101});
+ PORTB ^= (1 << 1) | (1 << 0); // What's wrong here?
+ checkAnswer(0b1001, PORTB);
+
+ newline();
+
+ PORTB = 0b1100;
+ print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
+ print("^ {b:0>4} // (bitmask)\n", .{0b0011});
+ PORTB ^= (1 << 1) & (1 << 0); // What's wrong here?
+ checkAnswer(0b1111, PORTB);
+
+ newline();
+
+ print("Set pins with OR on PORTB\n", .{});
+ print("-------------------------\n", .{});
+
+ PORTB = 0b1001; // reset PORTB
+ print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
+ print("| {b:0>4} // (bitmask)\n", .{0b0100});
+ PORTB = PORTB ??? (1 << 2); // What's missing here?
+ checkAnswer(0b1101, PORTB);
+
+ newline();
+
+ PORTB = 0b1001; // reset PORTB
+ print(" {b:0>4} // (reset state)\n", .{PORTB});
+ print("| {b:0>4} // (bitmask)\n", .{0b0100});
+ PORTB ??? (1 << 2); // What's missing here?
+ checkAnswer(0b1101, PORTB);
+
+ newline();
+
+ print("Clear pins with AND and NOT on PORTB\n", .{});
+ print("------------------------------------\n", .{});
+
+ PORTB = 0b1110; // reset PORTB
+ print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
+ print("& {b:0>4} // (bitmask)\n", .{0b1011});
+ PORTB = PORTB & ???@as(u4, 1 << 2); // What character is missing here?
+ checkAnswer(0b1010, PORTB);
+
+ newline();
+
+ PORTB = 0b0111; // reset PORTB
+ print(" {b:0>4} // (reset state)\n", .{PORTB});
+ print("& {b:0>4} // (bitmask)\n", .{0b1110});
+ PORTB &= ~(1 << 0); // What's missing here?
+ checkAnswer(0b0110, PORTB);
+
+ newline();
+ newline();
+}
+
+// ************************************************************************
+// IN-DEPTH EXPLANATIONS BELOW
+// ************************************************************************
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+// ------------------------------------------------------------------------
+// Toggling bits with XOR:
+// ------------------------------------------------------------------------
+// XOR stands for "exclusive or". We can toggle bits with the ^ (XOR)
+// bitwise operator, like so:
+//
+//
+// In order to output a 1, the logic of an XOR operation requires that the
+// two input bits are of different values. Therefore, 0 ^ 1 and 1 ^ 0 will
+// both yield a 1 but 0 ^ 0 and 1 ^ 1 will output 0. XOR's unique behavior
+// of outputting a 0 when both inputs are 1s is what makes it different from
+// the OR operator; it also gives us the ability to toggle bits by putting
+// 1s into our bitmask.
+//
+// - 1s in our bitmask operand, can be thought of as causing the
+// corresponding bits in the other operand to flip to the opposite value.
+// - 0s cause no change.
+//
+// The 0s in our bitmask preserve these values
+// -XOR op- ---expanded--- in the output.
+// _______________/
+// / /
+// 1100 1 1 0 0
+// ^ 0101 0 1 0 1 (bitmask)
+// ------ - - - -
+// = 1001 1 0 0 1 <- This bit was already cleared.
+// \_______\
+// \
+// We can think of these bits having flipped
+// because of the presence of 1s in those columns
+// of our bitmask.
+//
+// Now let's take a look at setting bits with the | operator.
+//
+//
+//
+//
+//
+// ------------------------------------------------------------------------
+// Setting bits with OR:
+// ------------------------------------------------------------------------
+// We can set bits on PORTB with the | (OR) operator, like so:
+//
+// var PORTB: u4 = 0b1001;
+// PORTB = PORTB | 0b0010;
+// print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011
+//
+// -OR op- ---expanded---
+// _ Set only this bit.
+// /
+// 1001 1 0 0 1
+// | 0010 0 0 1 0 (bitmask)
+// ------ - - - -
+// = 1011 1 0 1 1
+// \___\_______\
+// \
+// These bits remain untouched because OR-ing with
+// a 0 effects no change.
+//
+// ------------------------------------------------------------------------
+// To create a bitmask like 0b0010 used above:
+//
+// 1. First, shift the value 1 over one place with the bitwise << (shift
+// left) operator as indicated below:
+// 1 << 0 -> 0001
+// 1 << 1 -> 0010 <-- Shift 1 one place to the left
+// 1 << 2 -> 0100
+// 1 << 3 -> 1000
+//
+// This allows us to rewrite the above code like this:
+//
+// var PORTB: u4 = 0b1001;
+// PORTB = PORTB | (1 << 1);
+// print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011
+//
+// Finally, as in the C language, Zig allows us to use the |= operator, so
+// we can rewrite our code again in an even more compact and idiomatic
+// form: PORTB |= (1 << 1)
+
+// So now we've covered how to toggle and set bits. What about clearing
+// them? Well, this is where Zig throws us a curve ball. Don't worry we'll
+// go through it step by step.
+//
+//
+//
+//
+//
+// ------------------------------------------------------------------------
+// Clearing bits with AND and NOT:
+// ------------------------------------------------------------------------
+// We can clear bits with the & (AND) bitwise operator, like so:
+
+// PORTB = 0b1110; // reset PORTB
+// PORTB = PORTB & 0b1011;
+// print("PORTB: {b:0>4}\n", .{PORTB}); // output -> 1010
+//
+// - 0s clear bits when used in conjunction with a bitwise AND.
+// - 1s do nothing, thus preserving the original bits.
+//
+// -AND op- ---expanded---
+// __________ Clear only this bit.
+// /
+// 1110 1 1 1 0
+// & 1011 1 0 1 1 (bitmask)
+// ------ - - - -
+// = 1010 1 0 1 0 <- This bit was already cleared.
+// \_______\
+// \
+// These bits remain untouched because AND-ing with a
+// 1 preserves the original bit value whether 0 or 1.
+//
+// ------------------------------------------------------------------------
+// We can use the ~ (NOT) operator to easily create a bitmask like 1011:
+//
+// 1. First, shift the value 1 over two places with the bit-wise << (shift
+// left) operator as indicated below:
+// 1 << 0 -> 0001
+// 1 << 1 -> 0010
+// 1 << 2 -> 0100 <- The 1 has been shifted two places to the left
+// 1 << 3 -> 1000
+//
+// 2. The second step in creating our bitmask is to invert the bits
+// ~0100 -> 1011
+// in C we would write this as:
+// ~(1 << 2) -> 1011
+//
+// But if we try to compile ~(1 << 2) in Zig, we'll get an error:
+// unable to perform binary not operation on type 'comptime_int'
+//
+// Before Zig can invert our bits, it needs to know the number of
+// bits it's being asked to invert.
+//
+// We do this with the @as (cast as) built-in like this:
+// @as(u4, 1 << 2) -> 0100
+//
+// Finally, we can invert our new mask by placing the NOT ~ operator
+// before our expression, like this:
+// ~@as(u4, 1 << 2) -> 1011
+//
+// If you are offput by the fact that you can't simply invert bits like
+// you can in languages such as C without casting to a particular size
+// of integer, you're not alone. However, this is actually another
+// instance where Zig is really helpful because it protects you from
+// difficult to debug integer overflow bugs that can have you tearing
+// your hair out. In the interest of keeping things sane, Zig requires
+// you simply to tell it the size of number you are inverting. In the
+// words of Andrew Kelley, "If you want to invert the bits of an
+// integer, zig has to know how many bits there are."
+//
+// For more insight into the Zig team's position on why the language
+// takes the approach it does with the ~ operator, take a look at
+// Andrew's comments on the following github issue:
+// https://github.com/ziglang/zig/issues/1382#issuecomment-414459529
+//
+// Whew, so after all that what we end up with is:
+// PORTB = PORTB & ~@as(u4, 1 << 2);
+//
+// We can shorten this with the &= combined AND and assignment operator,
+// which applies the AND operator on PORTB and then reassigns PORTB. Here's
+// what that looks like:
+// PORTB &= ~@as(u4, 1 << 2);
+//
+
+// ------------------------------------------------------------------------
+// Conclusion
+// ------------------------------------------------------------------------
+//
+// While the examples in this quiz have used only 4-bit wide variables,
+// working with 8 bits is no different. Here's an example where we set
+// every other bit beginning with the two's place:
+
+// var PORTD: u8 = 0b0000_0000;
+// print("PORTD: {b:0>8}\n", .{PORTD});
+// PORTD |= (1 << 1);
+// PORTD = setBit(u8, PORTD, 3);
+// PORTD |= (1 << 5) | (1 << 7);
+// print("PORTD: {b:0>8} // set every other bit\n", .{PORTD});
+// PORTD = ~PORTD;
+// print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD});
+// newline();
+//
+// // Here we clear every other bit beginning with the two's place.
+//
+// PORTD = 0b1111_1111;
+// print("PORTD: {b:0>8}\n", .{PORTD});
+// PORTD &= ~@as(u8, 1 << 1);
+// PORTD = clearBit(u8, PORTD, 3);
+// PORTD &= ~@as(u8, (1 << 5) | (1 << 7));
+// print("PORTD: {b:0>8} // clear every other bit\n", .{PORTD});
+// PORTD = ~PORTD;
+// print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD});
+// newline();
+
+// ----------------------------------------------------------------------------
+// Here are some helper functions for manipulating bits
+// ----------------------------------------------------------------------------
+
+// Functions for setting, clearing, and toggling a single bit
+fn setBit(comptime T: type, byte: T, comptime bit_pos: T) !T {
+ return byte | (1 << bit_pos);
+}
+
+test "setBit" {
+ try testing.expectEqual(setBit(u8, 0b0000_0000, 3), 0b0000_1000);
+}
+
+fn clearBit(comptime T: type, byte: T, comptime bit_pos: T) T {
+ return byte & ~@as(T, (1 << bit_pos));
+}
+
+test "clearBit" {
+ try testing.expectEqual(clearBit(u8, 0b1111_1111, 0), 0b1111_1110);
+}
+
+fn toggleBit(comptime T: type, byte: T, comptime bit_pos: T) T {
+ return byte ^ (1 << bit_pos);
+}
+
+test "toggleBit" {
+ var byte = toggleBit(u8, 0b0000_0000, 0);
+ try testing.expectEqual(byte, 0b0000_0001);
+ byte = toggleBit(u8, byte, 0);
+ try testing.expectEqual(byte, 0b0000_0000);
+}
+
+// ----------------------------------------------------------------------------
+// Some additional functions for setting, clearing, and toggling multiple bits
+// at once with a tuple because, hey, why not?
+// ----------------------------------------------------------------------------
+//
+
+fn createBitmask(comptime T: type, comptime bits: anytype) !T {
+ comptime var bitmask: T = 0;
+ inline for (bits) |bit| {
+ if (bit >= @bitSizeOf(T)) return error.BitPosTooLarge;
+ if (bit < 0) return error.BitPosTooSmall;
+
+ bitmask |= (1 << bit);
+ }
+ return bitmask;
+}
+
+test "creating bitmasks from a tuple" {
+ try testing.expectEqual(createBitmask(u8, .{0}), 0b0000_0001);
+ try testing.expectEqual(createBitmask(u8, .{1}), 0b0000_0010);
+ try testing.expectEqual(createBitmask(u8, .{2}), 0b0000_0100);
+ try testing.expectEqual(createBitmask(u8, .{3}), 0b0000_1000);
+ //
+ try testing.expectEqual(createBitmask(u8, .{ 0, 4 }), 0b0001_0001);
+ try testing.expectEqual(createBitmask(u8, .{ 1, 5 }), 0b0010_0010);
+ try testing.expectEqual(createBitmask(u8, .{ 2, 6 }), 0b0100_0100);
+ try testing.expectEqual(createBitmask(u8, .{ 3, 7 }), 0b1000_1000);
+
+ try testing.expectError(error.BitPosTooLarge, createBitmask(u4, .{4}));
+}
+
+fn setBits(byte: u8, bits: anytype) !u8 {
+ const bitmask = try createBitmask(u8, bits);
+ return byte | bitmask;
+}
+
+test "setBits" {
+ try testing.expectEqual(setBits(0b0000_0000, .{0}), 0b0000_0001);
+ try testing.expectEqual(setBits(0b0000_0000, .{7}), 0b1000_0000);
+
+ try testing.expectEqual(setBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);
+ try testing.expectEqual(setBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);
+
+ try testing.expectEqual(setBits(0b0000_0000, .{ 2, 3, 4, 5 }), 0b0011_1100);
+
+ try testing.expectError(error.BitPosTooLarge, setBits(0b1111_1111, .{8}));
+ try testing.expectError(error.BitPosTooSmall, setBits(0b1111_1111, .{-1}));
+}
+
+fn clearBits(comptime byte: u8, comptime bits: anytype) !u8 {
+ const bitmask: u8 = try createBitmask(u8, bits);
+ return byte & ~@as(u8, bitmask);
+}
+
+test "clearBits" {
+ try testing.expectEqual(clearBits(0b1111_1111, .{0}), 0b1111_1110);
+ try testing.expectEqual(clearBits(0b1111_1111, .{7}), 0b0111_1111);
+
+ try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
+ try testing.expectEqual(clearBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
+
+ try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 6, 7 }), 0b0011_1100);
+
+ try testing.expectError(error.BitPosTooLarge, clearBits(0b1111_1111, .{8}));
+ try testing.expectError(error.BitPosTooSmall, clearBits(0b1111_1111, .{-1}));
+}
+
+fn toggleBits(comptime byte: u8, comptime bits: anytype) !u8 {
+ const bitmask = try createBitmask(u8, bits);
+ return byte ^ bitmask;
+}
+
+test "toggleBits" {
+ try testing.expectEqual(toggleBits(0b0000_0000, .{0}), 0b0000_0001);
+ try testing.expectEqual(toggleBits(0b0000_0000, .{7}), 0b1000_0000);
+
+ try testing.expectEqual(toggleBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
+ try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);
+
+ try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_0000);
+ try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3 }), 0b0000_0000);
+
+ try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 2, 4, 6 }), 0b0101_0101);
+
+ try testing.expectError(error.BitPosTooLarge, toggleBits(0b1111_1111, .{8}));
+ try testing.expectError(error.BitPosTooSmall, toggleBits(0b1111_1111, .{-1}));
+}
+
+// ----------------------------------------------------------------------------
+// Utility functions
+// ----------------------------------------------------------------------------
+
+fn newline() void {
+ print("\n", .{});
+}
+
+fn checkAnswer(expected: u4, answer: u4) void {
+ if (expected != answer) {
+ print("*************************************************************\n", .{});
+ print("= {b:0>4} <- INCORRECT! THE EXPECTED OUTPUT IS {b:0>4}\n", .{ answer, expected });
+ print("*************************************************************\n", .{});
+ } else {
+ print("= {b:0>4}", .{answer});
+ }
+ newline();
+}
diff --git a/exercises/114_packed.zig b/exercises/114_packed.zig
new file mode 100644
index 0000000..1058f76
--- /dev/null
+++ b/exercises/114_packed.zig
@@ -0,0 +1,175 @@
+//
+// We've already learned plenty about bit manipulation using bitwise operations
+// in exercices 097 and 098 and in quiz 110. The techniques we already know work
+// just fine, but creating masks and shifting individual bits around can become
+// quite tedious and unwieldy pretty quickly.
+// What if there was a better, a more convenient way to control invidivual bits?
+//
+// Luckily, Zig has a keyword for exactly this purpose:
+//
+// packed
+//
+// It doesn't do anything on its own, to unlock its potential (and to get our
+// program to compile) we have to attach it either to a struct or to a union
+// declaration:
+//
+// const Foo = packed struct { ... };
+// const Bar = packed union { ... };
+//
+// Now, what does this keyword even do?
+// To answer this question we first have to talk about *container layouts*.
+//
+// Plain structs and unions use the `auto` layout; it gives no guarantees about
+// their size or the order of the fields they contain, both are fully up to the
+// compiler (though both size and field order *are* guaranteed to be the same
+// across any single compilation unit).
+//
+// Attaching the `packed` keyword to a container makes it use `packed` layout:
+// Suddenly, all of its fields are *packed* together tightly without any padding
+// in between and their order is guaranteed to be the same as the one specified
+// in our source code. For structs, the size of the container is guaranteed to
+// be the sum of the (bit-)sizes of all of its fields. For unions, all fields
+// have to have the exact same (bit-)size (no padding allowed!); the union itself
+// is also guaranteed to be exactly of this size.
+//
+// If you're familiar with C, you might have already heard of structure packing
+// in a different context: arranging fields in a way that minimizes the amount
+// of alignment padding between them (or having the compiler do it for you).
+// This is *not* what Zig's `packed` keyword is for!
+//
+// Try to make the comptime assertions below pass:
+
+const PackedStruct = packed struct {
+ a: u2,
+ b: u?,
+};
+
+comptime {
+ assert(@bitSizeOf(PackedStruct) == 6);
+}
+
+const PackedUnion = packed union {
+ a: bool,
+ b: u?,
+};
+
+comptime {
+ assert(@bitSizeOf(PackedUnion) == 1);
+}
+
+// Now, how can we use this new knowledge to manipulate some bits?
+//
+// As you might have already guessed, `packed` containers are very useful for
+// representing bitflags or other tightly packed collections of bit-sized values
+// often found in file headers and network protocols.
+//
+// Let's take a look at a real-life example:
+// The LZ4 compression format (†) specifies a frame format to describe compressed
+// data. Each LZ4 frame has a descriptor, and each descriptor contains a 'FLG'
+// byte that specifies the contents of its frame:
+
+/// | BitNb | 7-6 | 5 | 4 | 3 | 2 | 1 | 0 |
+/// | ------- |-------|-------|----------|------|----------|--------|------|
+/// |FieldName|Version|B.Indep|B.Checksum|C.Size|C.Checksum|Reserved|DictID|
+///
+const FLG = packed struct(u8) {
+ dict_id: bool,
+ reserved: u1 = 0,
+ content_checksum: bool,
+ content_size: bool,
+ block_checksum: bool,
+ block_indepencence: bool,
+ version: u2,
+};
+
+// Wait, what's with the `(u8)` after the `struct` keyword? What do integers have
+// to do with all of this?
+// Well, this is a good opportunity to come clear about something:
+// packed structs and packed unions aren't actually structs or unions at all...
+// They are merely integers in disguise! For all intents and purposes, their
+// fields are just convenient names for ranges of their underlying bits. To make
+// it easier to enforce size requirements for packed containers, Zig allows us
+// to specify a *backing integer* for them, just like for enums.
+//
+// In the case of `FLG`, we want our struct to occupy exactly a single byte, so
+// we specify `u8` as the backing integer. It's safe to convert between a packed
+// container and its backing integer using the builtin `@bitCast`.
+// The LZ4 spec also mandates that reserved bits must always be zero, so it's
+// good practice to set `0` as a default value for `reserved`.
+//
+// The fields of a packed struct start at the least significant bit of its backing
+// integer and end at its most significant bit. This is the case no matter what
+// endianness our target has.
+//
+// Try to silence the complaints below:
+
+const Bits = packed struct(u4) {
+ a: u1 = 0,
+ b: u1 = 0,
+ c: u1 = 0,
+ d: u1 = 0,
+};
+
+pub fn main() void {
+ {
+ const expected: Bits = @bitCast(@as(u4, 0b1000));
+ const my_bits: Bits = .{};
+ if (my_bits != expected) complain(my_bits, expected, @src());
+ }
+
+ {
+ const expected: Bits = @bitCast(@as(u4, 0b0001));
+ const my_bits: Bits = .{};
+ if (my_bits != expected) complain(my_bits, expected, @src());
+ }
+
+ {
+ const expected: Bits = @bitCast(@as(u4, 0b0010));
+ const my_bits: Bits = .{};
+ if (my_bits != expected) complain(my_bits, expected, @src());
+ }
+
+ {
+ const expected: Bits = @bitCast(@as(u4, 0b0011));
+ const my_bits: Bits = .{};
+ if (my_bits != expected) complain(my_bits, expected, @src());
+ }
+
+ {
+ const expected: Bits = @bitCast(@as(u4, 0b1101));
+ const my_bits: Bits = .{};
+ if (my_bits != expected) complain(my_bits, expected, @src());
+ }
+}
+
+// As we can see, equality comparisons (`==` and `!=`) work for packed structs.
+// They also work for packed unions. However, since packed containers are not
+// naturally ordered, we can't use any other comparison operators on them.
+//
+// It's also possible to use packed containers in `switch` statements, which we
+// will cover in the next exercise!
+//
+// Since packed containers make very strong guarantees about their memory layout,
+// only a handful of types are eligible to be part of them.
+// The following types are allowed as field types:
+//
+// - integers
+// - floats
+// - bool
+// - void
+// - enums with explicit backing integers
+// - packed unions
+// - packed structs
+//
+
+const std = @import("std");
+const assert = std.debug.assert;
+
+fn complain(my_bits: Bits, expected: Bits, src_loc: std.builtin.SourceLocation) void {
+ std.debug.print(
+ "That's not quite right! You've got 0b{b:0>4}, but we want 0b{b:0>4} in line {d}.\n",
+ .{ @as(u4, @bitCast(my_bits)), @as(u4, @bitCast(expected)), src_loc.line },
+ );
+}
+
+// (†) https://github.com/lz4/lz4/blob/5c4c1fb2354133e1f3b087a341576985f8114bd5/doc/lz4_Frame_format.md#frame-descriptor
diff --git a/exercises/115_packed2.zig b/exercises/115_packed2.zig
new file mode 100644
index 0000000..2b6b558
--- /dev/null
+++ b/exercises/115_packed2.zig
@@ -0,0 +1,78 @@
+//
+// We've already learned about switch statements in exercises 030, 031 and 108.
+// They also work with packed containers:
+
+const S = packed struct(u2) {
+ a: bool,
+ b: i1,
+};
+
+// Try to make it compile without adding an `else` prong!
+
+comptime {
+ const s: S = .{ .a = true, .b = -1 };
+ switch (s) {
+ .{ .a = true, .b = -1 } => {}, // ok!
+ .{ .a = true, .b = ??? },
+ .{ .a = ???, .b = 0 },
+ .{ .a = ???, .b = ??? },
+ => @compileError("We don't want to end up here!"),
+ }
+}
+
+// As we can see, switching on packed structs is pretty straightforward.
+// When switching on packed unions however, we'll realize that a packed
+// union never keeps track of its active tag, not even in debug mode! This
+// means that packed unions compare solely by their bit pattern (again, just
+// like integers).
+
+const U = packed union(u2) {
+ a: u2,
+ b: i2,
+};
+
+// Find and remove the duplicate case!
+
+comptime {
+ const u: U = .{ .a = 3 };
+ switch (u) {
+ .{ .a = 3 } => {}, // ok!
+ .{ .a = 2 },
+ .{ .b = 1 },
+ .{ .b = -1 },
+ .{ .a = 0 },
+ => @compileError("We don't want to end up here!"),
+ }
+}
+
+// Since packed unions don't have the concept of an active tag, it's always legal
+// to access any of their fields. This can be useful to view the same data from
+// different perspectives seamlessly.
+//
+// Try to make the float below negative:
+
+/// IEEE 754 half precision float
+const Float = packed union(u16) {
+ value: f16,
+ bits: packed struct(u16) {
+ mantissa: u10,
+ exponent: u5,
+ sign: u1,
+ },
+};
+
+pub fn main() void {
+ // Reminder: if the sign bit of a float is set, the number is negative!
+
+ var number: Float = .{ .value = 2.34 };
+ number.bits.??? = ???;
+ if (number.value != -2.34) {
+ std.debug.print("Make it negative!\n", .{});
+ }
+}
+
+// This concludes our introduction to packed containers. The next time you need
+// control over individual bits, keep them in mind as a potent alternative!
+//
+
+const std = @import("std");
diff --git a/patches/patches/091_async8.patch b/patches/patches/091_async8.patch
new file mode 100644
index 0000000..865292f
--- /dev/null
+++ b/patches/patches/091_async8.patch
@@ -0,0 +1,11 @@
+--- exercises/091_async8.zig 2026-04-02 10:49:27.925721496 +0200
++++ answers/091_async8.zig 2026-04-02 10:49:31.694795212 +0200
+@@ -43,7 +43,7 @@
+ // 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;
++ queue.putOne(io, @intCast(i)) catch return;
+ }
+ // Signal that we're done sending.
+ queue.close(io);
diff --git a/patches/patches/092_interfaces.patch b/patches/patches/092_interfaces.patch
deleted file mode 100644
index 36d8784..0000000
--- a/patches/patches/092_interfaces.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- exercises/092_interfaces.zig 2023-10-03 22:15:22.125574535 +0200
-+++ answers/092_interfaces.zig 2023-10-05 20:04:07.259437354 +0200
-@@ -106,7 +106,7 @@
- for (my_insects) |insect| {
- // Almost done! We want to print() each insect with a
- // single method call here.
-- ???
-+ insect.print();
- }
- }
-
diff --git a/patches/patches/093_hello_c.patch b/patches/patches/093_hello_c.patch
deleted file mode 100644
index fe1f853..0000000
--- a/patches/patches/093_hello_c.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- exercises/093_hello_c.zig 2023-10-03 22:15:22.125574535 +0200
-+++ answers/093_hello_c.zig 2023-10-05 20:04:07.262770750 +0200
-@@ -54,7 +54,7 @@
- //
- // In this exercise we use 'write' to output 17 chars,
- // but something is still missing...
-- const c_res = write(2, "Hello C from Zig!", 17);
-+ const c_res = c.write(2, "Hello C from Zig!", 17);
-
- // let's see what the result from C is:
- std.debug.print(" - C result is {d} chars written.\n", .{c_res});
diff --git a/patches/patches/094_c_math.patch b/patches/patches/094_c_math.patch
deleted file mode 100644
index f8c7620..0000000
--- a/patches/patches/094_c_math.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- exercises/094_c_math.zig 2024-02-28 12:50:35.789939935 +0100
-+++ answers/094_c_math.zig 2024-02-28 12:53:57.910309471 +0100
-@@ -26,7 +26,7 @@
-
- const c = @cImport({
- // What do we need here?
-- ???
-+ @cInclude("math.h");
- });
-
- pub fn main() !void {
diff --git a/patches/patches/095_for3.patch b/patches/patches/095_for3.patch
deleted file mode 100644
index a158b31..0000000
--- a/patches/patches/095_for3.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- exercises/095_for3.zig 2026-02-27 19:33:59
-+++ answers/095_for3.zig 2026-02-27 19:33:38
-@@ -56,7 +56,7 @@
-
- // I want to print every number between 1 and 20 that is NOT
- // divisible by 3 or 5.
-- for (???) |n| {
-+ for (1..21) |n| {
-
- // The '%' symbol is the "modulo" operator and it
- // returns the remainder after division.
-@@ -68,7 +68,7 @@
- std.debug.print("\n", .{});
-
- // Let's also print every number from 1 through 15
-- for (???) |n| {
-+ for (1..16) |n| {
- std.debug.print("{} ", .{n});
- }
- std.debug.print("\n", .{});
diff --git a/patches/patches/095_interfaces.patch b/patches/patches/095_interfaces.patch
new file mode 100644
index 0000000..cb5fd62
--- /dev/null
+++ b/patches/patches/095_interfaces.patch
@@ -0,0 +1,11 @@
+--- exercises/095_interfaces.zig 2025-08-15 15:17:57.839348063 +0200
++++ answers/095_interfaces.zig 2026-04-03 13:09:13.722917764 +0200
+@@ -106,7 +106,7 @@
+ for (my_insects) |insect| {
+ // Almost done! We want to print() each insect with a
+ // single method call here.
+- ???
++ insect.print();
+ }
+ }
+
diff --git a/patches/patches/096_hello_c.patch b/patches/patches/096_hello_c.patch
new file mode 100644
index 0000000..f5fe06f
--- /dev/null
+++ b/patches/patches/096_hello_c.patch
@@ -0,0 +1,11 @@
+--- exercises/096_hello_c.zig 2025-08-15 15:17:57.839348063 +0200
++++ answers/096_hello_c.zig 2026-04-03 13:09:26.195163128 +0200
+@@ -54,7 +54,7 @@
+ //
+ // In this exercise we use 'write' to output 17 chars,
+ // but something is still missing...
+- const c_res = write(2, "Hello C from Zig!", 17);
++ const c_res = c.write(2, "Hello C from Zig!", 17);
+
+ // let's see what the result from C is:
+ std.debug.print(" - C result is {d} chars written.\n", .{c_res});
diff --git a/patches/patches/096_memory_allocation.patch b/patches/patches/096_memory_allocation.patch
deleted file mode 100644
index c26eeeb..0000000
--- a/patches/patches/096_memory_allocation.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- exercises/096_memory_allocation.zig 2023-11-21 14:55:33.805678390 +0100
-+++ answers/096_memory_allocation.zig 2023-11-21 14:56:00.236163484 +0100
-@@ -64,7 +64,7 @@
- const allocator = arena.allocator();
-
- // allocate memory for this array
-- const avg: []f64 = ???;
-+ const avg: []f64 = try allocator.alloc(f64, arr.len);
-
- runningAverage(arr, avg);
- std.debug.print("Running Average: ", .{});
diff --git a/patches/patches/097_bit_manipulation.patch b/patches/patches/097_bit_manipulation.patch
deleted file mode 100644
index 19ba876..0000000
--- a/patches/patches/097_bit_manipulation.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- exercises/097_bit_manipulation.zig 2025-05-12 21:25:03.395385743 +0200
-+++ answers/097_bit_manipulation.zig 2025-05-12 21:22:57.472986976 +0200
-@@ -80,7 +80,7 @@
- y ^= x;
-
- // What must be written here?
-- ???;
-+ x ^= y;
-
- print("x = {b}; y = {b}\n", .{ x, y });
- }
diff --git a/patches/patches/097_c_math.patch b/patches/patches/097_c_math.patch
new file mode 100644
index 0000000..917fe6b
--- /dev/null
+++ b/patches/patches/097_c_math.patch
@@ -0,0 +1,11 @@
+--- exercises/097_c_math.zig 2025-08-15 15:17:57.839348063 +0200
++++ answers/097_c_math.zig 2026-04-03 13:09:32.059278502 +0200
+@@ -26,7 +26,7 @@
+
+ const c = @cImport({
+ // What do we need here?
+- ???
++ @cInclude("math.h");
+ });
+
+ pub fn main() !void {
diff --git a/patches/patches/098_bit_manipulation2.patch b/patches/patches/098_bit_manipulation2.patch
deleted file mode 100644
index 5354a7b..0000000
--- a/patches/patches/098_bit_manipulation2.patch
+++ /dev/null
@@ -1,9 +0,0 @@
---- exercises/098_bit_manipulation2.zig 2023-10-03 22:15:22.125574535 +0200
-+++ answers/098_bit_manipulation2.zig 2023-10-05 20:04:07.286104520 +0200
-@@ -60,5 +60,5 @@
- // and if so, we know the given string is a pangram
- //
- // but what do we have to compare?
-- return bits == 0x..???;
-+ return bits == 0x3ffffff;
- }
diff --git a/patches/patches/098_for3.patch b/patches/patches/098_for3.patch
new file mode 100644
index 0000000..c280bc6
--- /dev/null
+++ b/patches/patches/098_for3.patch
@@ -0,0 +1,20 @@
+--- exercises/098_for3.zig 2026-03-20 19:23:48.873150100 +0100
++++ answers/098_for3.zig 2026-04-03 13:09:39.916433087 +0200
+@@ -56,7 +56,7 @@
+
+ // I want to print every number between 1 and 20 that is NOT
+ // divisible by 3 or 5.
+- for (???) |n| {
++ for (1..21) |n| {
+
+ // The '%' symbol is the "modulo" operator and it
+ // returns the remainder after division.
+@@ -68,7 +68,7 @@
+ std.debug.print("\n", .{});
+
+ // Let's also print every number from 1 through 15
+- for (???) |n| {
++ for (1..16) |n| {
+ std.debug.print("{} ", .{n});
+ }
+ std.debug.print("\n", .{});
diff --git a/patches/patches/099_formatting.patch b/patches/patches/099_formatting.patch
deleted file mode 100644
index a56b556..0000000
--- a/patches/patches/099_formatting.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- exercises/099_formatting.zig 2024-11-07 21:45:10.459123650 +0100
-+++ answers/099_formatting.zig 2024-11-07 21:43:55.154345991 +0100
-@@ -131,7 +131,7 @@
- for (0..size) |b| {
- // What formatting is needed here to make our columns
- // nice and straight?
-- print("{???} ", .{(a + 1) * (b + 1)});
-+ print("{d:>3} ", .{(a + 1) * (b + 1)});
- }
-
- // After each row we use double line feed:
diff --git a/patches/patches/099_memory_allocation.patch b/patches/patches/099_memory_allocation.patch
new file mode 100644
index 0000000..d83a0ae
--- /dev/null
+++ b/patches/patches/099_memory_allocation.patch
@@ -0,0 +1,11 @@
+--- exercises/099_memory_allocation.zig 2025-08-15 15:17:57.839348063 +0200
++++ answers/099_memory_allocation.zig 2026-04-03 13:09:47.403580391 +0200
+@@ -64,7 +64,7 @@
+ const allocator = arena.allocator();
+
+ // allocate memory for this array
+- const avg: []f64 = ???;
++ const avg: []f64 = try allocator.alloc(f64, arr.len);
+
+ runningAverage(arr, avg);
+ std.debug.print("Running Average: ", .{});
diff --git a/patches/patches/100_bit_manipulation.patch b/patches/patches/100_bit_manipulation.patch
new file mode 100644
index 0000000..f70cc41
--- /dev/null
+++ b/patches/patches/100_bit_manipulation.patch
@@ -0,0 +1,11 @@
+--- exercises/100_bit_manipulation.zig 2025-08-15 15:17:57.839348063 +0200
++++ answers/100_bit_manipulation.zig 2026-04-02 10:51:15.795831343 +0200
+@@ -80,7 +80,7 @@
+ y ^= x;
+
+ // What must be written here?
+- ???;
++ x ^= y;
+
+ print("x = {b}; y = {b}\n", .{ x, y });
+ }
diff --git a/patches/patches/100_for4.patch b/patches/patches/100_for4.patch
deleted file mode 100644
index ad73e9a..0000000
--- a/patches/patches/100_for4.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- exercises/100_for4.zig 2023-10-03 22:15:22.125574535 +0200
-+++ answers/100_for4.zig 2023-10-05 20:04:07.296104707 +0200
-@@ -39,7 +39,7 @@
- const hex_nums = [_]u8{ 0xb, 0x2a, 0x77 };
- const dec_nums = [_]u8{ 11, 42, 119 };
-
-- for (hex_nums, ???) |hn, ???| {
-+ for (hex_nums, dec_nums) |hn, dn| {
- if (hn != dn) {
- print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn });
- return;
diff --git a/patches/patches/101_bit_manipulation2.patch b/patches/patches/101_bit_manipulation2.patch
new file mode 100644
index 0000000..5179272
--- /dev/null
+++ b/patches/patches/101_bit_manipulation2.patch
@@ -0,0 +1,9 @@
+--- exercises/101_bit_manipulation2.zig 2025-08-15 15:17:57.839348063 +0200
++++ answers/101_bit_manipulation2.zig 2026-04-02 10:51:15.797831382 +0200
+@@ -60,5 +60,5 @@
+ // and if so, we know the given string is a pangram
+ //
+ // but what do we have to compare?
+- return bits == 0x..???;
++ return bits == 0x3ffffff;
+ }
diff --git a/patches/patches/101_for5.patch b/patches/patches/101_for5.patch
deleted file mode 100644
index 00f19df..0000000
--- a/patches/patches/101_for5.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- exercises/101_for5.zig 2023-10-03 22:15:22.125574535 +0200
-+++ answers/101_for5.zig 2023-10-05 20:04:07.299438103 +0200
-@@ -51,7 +51,7 @@
-
- // We would like to number our list starting with 1, not 0.
- // How do we do that?
-- for (roles, gold, experience, ???) |c, g, e, i| {
-+ for (roles, gold, experience, 1..) |c, g, e, i| {
- const role_name = switch (c) {
- .wizard => "Wizard",
- .thief => "Thief",
diff --git a/patches/patches/102_formatting.patch b/patches/patches/102_formatting.patch
new file mode 100644
index 0000000..dba8aef
--- /dev/null
+++ b/patches/patches/102_formatting.patch
@@ -0,0 +1,11 @@
+--- exercises/102_formatting.zig 2026-03-20 19:23:48.873150100 +0100
++++ answers/102_formatting.zig 2026-04-02 10:51:15.799831421 +0200
+@@ -131,7 +131,7 @@
+ for (0..size) |b| {
+ // What formatting is needed here to make our columns
+ // nice and straight?
+- print("{???} ", .{(a + 1) * (b + 1)});
++ print("{d:>3} ", .{(a + 1) * (b + 1)});
+ }
+
+ // After each row we use double line feed:
diff --git a/patches/patches/102_testing.patch b/patches/patches/102_testing.patch
deleted file mode 100644
index 6d18bf6..0000000
--- a/patches/patches/102_testing.patch
+++ /dev/null
@@ -1,18 +0,0 @@
---- exercises/102_testing.zig 2025-10-24 12:54:56
-+++ answers/102_testing.zig 2025-10-24 12:56:33
-@@ -71,7 +71,7 @@
- // The corresponding test is not much different from the previous one. Except
- // that it contains an error that you need to correct.
- test "sub" {
-- try testing.expect(sub(10, 5) == 6);
-+ try testing.expect(sub(10, 5) == 5);
-
- try testing.expect(sub(3, 1.5) == 1.5);
- }
-@@ -92,5 +92,5 @@
-
- // Now we test if the function returns an error if we pass a zero as
- // denominator. But which error needs to be tested?
-- try testing.expectError(error.???, divide(15, 0));
-+ try testing.expectError(error.DivisionByZero, divide(15, 0));
- }
diff --git a/patches/patches/103_for4.patch b/patches/patches/103_for4.patch
new file mode 100644
index 0000000..7a7e161
--- /dev/null
+++ b/patches/patches/103_for4.patch
@@ -0,0 +1,11 @@
+--- exercises/103_for4.zig 2025-08-15 15:17:57.839348063 +0200
++++ answers/103_for4.zig 2026-04-02 10:51:15.801831460 +0200
+@@ -39,7 +39,7 @@
+ const hex_nums = [_]u8{ 0xb, 0x2a, 0x77 };
+ const dec_nums = [_]u8{ 11, 42, 119 };
+
+- for (hex_nums, ???) |hn, ???| {
++ for (hex_nums, dec_nums) |hn, dn| {
+ if (hn != dn) {
+ print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn });
+ return;
diff --git a/patches/patches/103_tokenization.patch b/patches/patches/103_tokenization.patch
deleted file mode 100644
index 941ca3b..0000000
--- a/patches/patches/103_tokenization.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- exercises/103_tokenization.zig 2026-02-27 19:25:11
-+++ answers/103_tokenization.zig 2026-02-27 19:26:04
-@@ -134,7 +134,7 @@
- ;
-
- // now the tokenizer, but what do we need here?
-- var it = std.mem.tokenizeAny(u8, poem, ???);
-+ var it = std.mem.tokenizeAny(u8, poem, " ,;!\n");
-
- // print all words and count them
- var cnt: usize = 0;
diff --git a/patches/patches/104_for5.patch b/patches/patches/104_for5.patch
new file mode 100644
index 0000000..ef77013
--- /dev/null
+++ b/patches/patches/104_for5.patch
@@ -0,0 +1,11 @@
+--- exercises/104_for5.zig 2025-08-15 15:17:57.839348063 +0200
++++ answers/104_for5.zig 2026-04-02 10:51:15.803831499 +0200
+@@ -51,7 +51,7 @@
+
+ // We would like to number our list starting with 1, not 0.
+ // How do we do that?
+- for (roles, gold, experience, ???) |c, g, e, i| {
++ for (roles, gold, experience, 1..) |c, g, e, i| {
+ const role_name = switch (c) {
+ .wizard => "Wizard",
+ .thief => "Thief",
diff --git a/patches/patches/104_threading.patch b/patches/patches/104_threading.patch
deleted file mode 100644
index e6fe0f4..0000000
--- a/patches/patches/104_threading.patch
+++ /dev/null
@@ -1,17 +0,0 @@
---- exercises/104_threading.zig 2026-04-01 23:31:10.073198955 +0200
-+++ answers/104_threading.zig 2026-04-01 23:29:51.314585919 +0200
-@@ -88,12 +88,12 @@
- defer handle.join();
-
- // Second thread
-- const handle2 = try std.Thread.spawn(.{}, thread_function, .{-4}); // that can't be right?
-+ const handle2 = try std.Thread.spawn(.{}, thread_function, .{2});
- defer handle2.join();
-
- // Third thread
- const handle3 = try std.Thread.spawn(.{}, thread_function, .{3});
-- defer ??? // <-- something is missing
-+ defer handle3.join();
-
- // After the threads have been started,
- // they run in parallel and we can still do some work in between.
diff --git a/patches/patches/105_testing.patch b/patches/patches/105_testing.patch
new file mode 100644
index 0000000..069b72b
--- /dev/null
+++ b/patches/patches/105_testing.patch
@@ -0,0 +1,18 @@
+--- exercises/105_testing.zig 2026-03-20 19:23:48.873150100 +0100
++++ answers/105_testing.zig 2026-04-02 10:51:15.805831538 +0200
+@@ -71,7 +71,7 @@
+ // The corresponding test is not much different from the previous one. Except
+ // that it contains an error that you need to correct.
+ test "sub" {
+- try testing.expect(sub(10, 5) == 6);
++ try testing.expect(sub(10, 5) == 5);
+
+ try testing.expect(sub(3, 1.5) == 1.5);
+ }
+@@ -92,5 +92,5 @@
+
+ // Now we test if the function returns an error if we pass a zero as
+ // denominator. But which error needs to be tested?
+- try testing.expectError(error.???, divide(15, 0));
++ try testing.expectError(error.DivisionByZero, divide(15, 0));
+ }
diff --git a/patches/patches/105_threading2.patch b/patches/patches/105_threading2.patch
deleted file mode 100644
index dfa5613..0000000
--- a/patches/patches/105_threading2.patch
+++ /dev/null
@@ -1,13 +0,0 @@
---- exercises/105_threading2.zig 2024-03-23 16:35:14.754540802 +0100
-+++ answers/105_threading2.zig 2024-03-23 16:38:00.577539733 +0100
-@@ -81,8 +81,8 @@
- defer handle1.join();
-
- // Second thread to calculate the minus numbers.
-- ???
--
-+ const handle2 = try std.Thread.spawn(.{}, thread_pi, .{ &pi_minus, 3, count });
-+ defer handle2.join();
- }
- // Here we add up the results.
- std.debug.print("PI ≈ {d:.8}\n", .{4 + pi_plus - pi_minus});
diff --git a/patches/patches/106_files.patch b/patches/patches/106_files.patch
deleted file mode 100644
index 27bcb56..0000000
--- a/patches/patches/106_files.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- exercises/106_files.zig 2026-01-09 22:41:19.373872684 +0100
-+++ answers/106_files.zig 2026-01-09 22:41:44.518372910 +0100
-@@ -41,7 +41,7 @@
- // by doing nothing
- //
- // we want to catch error.PathAlreadyExists and do nothing
-- ??? => {},
-+ error.PathAlreadyExists => {},
- // if there's any other unexpected error we just propagate it through
- else => return e,
- };
-@@ -61,7 +61,7 @@
- // but here we are not yet done writing to the file
- // if only there were a keyword in Zig that
- // allowed you to "defer" code execution to the end of the scope...
-- file.close(io);
-+ defer file.close(io);
-
- // you are not allowed to move these lines above the file closing line!
- var file_writer = file.writer(io, &.{});
diff --git a/patches/patches/106_tokenization.patch b/patches/patches/106_tokenization.patch
new file mode 100644
index 0000000..4792df6
--- /dev/null
+++ b/patches/patches/106_tokenization.patch
@@ -0,0 +1,11 @@
+--- exercises/106_tokenization.zig 2026-03-20 19:23:48.873150100 +0100
++++ answers/106_tokenization.zig 2026-04-02 10:51:15.807831578 +0200
+@@ -134,7 +134,7 @@
+ ;
+
+ // now the tokenizer, but what do we need here?
+- var it = std.mem.tokenizeAny(u8, poem, ???);
++ var it = std.mem.tokenizeAny(u8, poem, " ,;!\n");
+
+ // print all words and count them
+ var cnt: usize = 0;
diff --git a/patches/patches/107_files2.patch b/patches/patches/107_files2.patch
deleted file mode 100644
index 6820742..0000000
--- a/patches/patches/107_files2.patch
+++ /dev/null
@@ -1,26 +0,0 @@
---- exercises/107_files2.zig 2026-01-09 22:43:15.211177186 +0100
-+++ answers/107_files2.zig 2026-01-09 22:42:48.943654602 +0100
-@@ -39,7 +39,7 @@
- // initialize an array of u8 with all letter 'A'
- // we need to pick the size of the array, 64 seems like a good number
- // fix the initialization below
-- var content = ['A']*64;
-+ var content = [_]u8{'A'} ** 64;
- // this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`
- std.debug.print("{s}\n", .{content});
-
-@@ -50,12 +50,12 @@
- // can you go here to find a way to read the content?
- // https://ziglang.org/documentation/master/std/#std.Io.Reader
- // hint: look for a method that reads into a slice
-- const bytes_read = zig_read_the_file_or_i_will_fight_you(&content);
-+ const bytes_read = try reader.readSliceShort(&content);
-
- // Woah, too screamy. I know you're excited for zigling time but tone it down a bit.
- // Can you print only what we read from the file?
- std.debug.print("Successfully Read {d} bytes: {s}\n", .{
- bytes_read,
-- content, // change this line only
-+ content[0..bytes_read], // change this line only
- });
- }
diff --git a/patches/patches/107_threading.patch b/patches/patches/107_threading.patch
new file mode 100644
index 0000000..3f65ef4
--- /dev/null
+++ b/patches/patches/107_threading.patch
@@ -0,0 +1,17 @@
+--- exercises/107_threading.zig 2026-04-01 23:31:10.073198955 +0200
++++ answers/107_threading.zig 2026-04-02 10:51:15.809831617 +0200
+@@ -88,12 +88,12 @@
+ defer handle.join();
+
+ // Second thread
+- const handle2 = try std.Thread.spawn(.{}, thread_function, .{-4}); // that can't be right?
++ const handle2 = try std.Thread.spawn(.{}, thread_function, .{2});
+ defer handle2.join();
+
+ // Third thread
+ const handle3 = try std.Thread.spawn(.{}, thread_function, .{3});
+- defer ??? // <-- something is missing
++ defer handle3.join();
+
+ // After the threads have been started,
+ // they run in parallel and we can still do some work in between.
diff --git a/patches/patches/108_labeled_switch.patch b/patches/patches/108_labeled_switch.patch
deleted file mode 100644
index fa9dd67..0000000
--- a/patches/patches/108_labeled_switch.patch
+++ /dev/null
@@ -1,18 +0,0 @@
---- exercises/108_labeled_switch.zig 2024-09-20 12:09:24.370066539 +0200
-+++ answers/108_labeled_switch.zig 2024-09-20 12:09:06.499711739 +0200
-@@ -65,13 +65,13 @@
- // how would you fix it?
- pr: switch (PullRequestState.Draft) {
- PullRequestState.Draft => continue :pr PullRequestState.InReview,
-- PullRequestState.InReview => continue :pr PullRequestState.Rejected,
-+ PullRequestState.InReview => continue :pr PullRequestState.Approved,
- PullRequestState.Approved => continue :pr PullRequestState.Merged,
- PullRequestState.Rejected => {
- std.debug.print("The pull request has been rejected.\n", .{});
- return;
- },
-- PullRequestState.Merged => break, // Would you know where to break to?
-+ PullRequestState.Merged => break :pr, // Would you know where to break to?
- }
- std.debug.print("The pull request has been merged.\n", .{});
- }
diff --git a/patches/patches/108_threading2.patch b/patches/patches/108_threading2.patch
new file mode 100644
index 0000000..d71f254
--- /dev/null
+++ b/patches/patches/108_threading2.patch
@@ -0,0 +1,13 @@
+--- exercises/108_threading2.zig 2025-08-15 15:17:57.839348063 +0200
++++ answers/108_threading2.zig 2026-04-02 10:51:15.811831656 +0200
+@@ -81,8 +81,8 @@
+ defer handle1.join();
+
+ // Second thread to calculate the minus numbers.
+- ???
+-
++ const handle2 = try std.Thread.spawn(.{}, thread_pi, .{ &pi_minus, 3, count });
++ defer handle2.join();
+ }
+ // Here we add up the results.
+ std.debug.print("PI ≈ {d:.8}\n", .{4 + pi_plus - pi_minus});
diff --git a/patches/patches/109_files.patch b/patches/patches/109_files.patch
new file mode 100644
index 0000000..ac59d70
--- /dev/null
+++ b/patches/patches/109_files.patch
@@ -0,0 +1,20 @@
+--- exercises/109_files.zig 2026-03-20 19:23:48.874150121 +0100
++++ answers/109_files.zig 2026-04-02 10:51:15.813831695 +0200
+@@ -41,7 +41,7 @@
+ // by doing nothing
+ //
+ // we want to catch error.PathAlreadyExists and do nothing
+- ??? => {},
++ error.PathAlreadyExists => {},
+ // if there's any other unexpected error we just propagate it through
+ else => return e,
+ };
+@@ -61,7 +61,7 @@
+ // but here we are not yet done writing to the file
+ // if only there were a keyword in Zig that
+ // allowed you to "defer" code execution to the end of the scope...
+- file.close(io);
++ defer file.close(io);
+
+ // you are not allowed to move these lines above the file closing line!
+ var file_writer = file.writer(io, &.{});
diff --git a/patches/patches/109_vectors.patch b/patches/patches/109_vectors.patch
deleted file mode 100644
index bf18cc0..0000000
--- a/patches/patches/109_vectors.patch
+++ /dev/null
@@ -1,13 +0,0 @@
---- exercises/109_vectors.zig 2024-11-07 14:57:09.673383618 +0100
-+++ answers/109_vectors.zig 2024-11-07 14:22:59.069150138 +0100
-@@ -121,8 +121,8 @@
-
- const Vec4 = @Vector(4, f32);
- fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 {
-- const abs_diff_vec = ???;
-- const max_diff = @reduce(???, abs_diff_vec);
-+ const abs_diff_vec = @abs(a - b);
-+ const max_diff = @reduce(.Max, abs_diff_vec);
- return max_diff;
- }
-
diff --git a/patches/patches/110_files2.patch b/patches/patches/110_files2.patch
new file mode 100644
index 0000000..64dc193
--- /dev/null
+++ b/patches/patches/110_files2.patch
@@ -0,0 +1,26 @@
+--- exercises/110_files2.zig 2026-03-20 19:23:48.874150121 +0100
++++ answers/110_files2.zig 2026-04-02 10:51:15.815831734 +0200
+@@ -39,7 +39,7 @@
+ // initialize an array of u8 with all letter 'A'
+ // we need to pick the size of the array, 64 seems like a good number
+ // fix the initialization below
+- var content = ['A']*64;
++ var content = [_]u8{'A'} ** 64;
+ // this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`
+ std.debug.print("{s}\n", .{content});
+
+@@ -50,12 +50,12 @@
+ // can you go here to find a way to read the content?
+ // https://ziglang.org/documentation/master/std/#std.Io.Reader
+ // hint: look for a method that reads into a slice
+- const bytes_read = zig_read_the_file_or_i_will_fight_you(&content);
++ const bytes_read = try reader.readSliceShort(&content);
+
+ // Woah, too screamy. I know you're excited for zigling time but tone it down a bit.
+ // Can you print only what we read from the file?
+ std.debug.print("Successfully Read {d} bytes: {s}\n", .{
+ bytes_read,
+- content, // change this line only
++ content[0..bytes_read], // change this line only
+ });
+ }
diff --git a/patches/patches/110_quiz9.patch b/patches/patches/110_quiz9.patch
deleted file mode 100644
index 9d9b864..0000000
--- a/patches/patches/110_quiz9.patch
+++ /dev/null
@@ -1,56 +0,0 @@
---- exercises/110_quiz9.zig 2025-02-08 13:19:48.522641785 -0800
-+++ answers/110_quiz9.zig 2025-02-10 17:42:04.525004335 -0800
-@@ -108,7 +108,7 @@
- PORTB = 0b1100;
- print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
- print("^ {b:0>4} // (bitmask)\n", .{0b0101});
-- PORTB ^= (1 << 1) | (1 << 0); // What's wrong here?
-+ PORTB ^= (1 << 2) | (1 << 0);
- checkAnswer(0b1001, PORTB);
-
- newline();
-@@ -116,7 +116,7 @@
- PORTB = 0b1100;
- print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
- print("^ {b:0>4} // (bitmask)\n", .{0b0011});
-- PORTB ^= (1 << 1) & (1 << 0); // What's wrong here?
-+ PORTB ^= (1 << 1) | (1 << 0);
- checkAnswer(0b1111, PORTB);
-
- newline();
-@@ -170,7 +170,7 @@
- PORTB = 0b1001; // reset PORTB
- print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
- print("| {b:0>4} // (bitmask)\n", .{0b0100});
-- PORTB = PORTB ??? (1 << 2); // What's missing here?
-+ PORTB = PORTB | (1 << 2);
- checkAnswer(0b1101, PORTB);
-
- newline();
-@@ -178,7 +178,7 @@
- PORTB = 0b1001; // reset PORTB
- print(" {b:0>4} // (reset state)\n", .{PORTB});
- print("| {b:0>4} // (bitmask)\n", .{0b0100});
-- PORTB ??? (1 << 2); // What's missing here?
-+ PORTB |= (1 << 2);
- checkAnswer(0b1101, PORTB);
-
- newline();
-@@ -269,7 +269,7 @@
- PORTB = 0b1110; // reset PORTB
- print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
- print("& {b:0>4} // (bitmask)\n", .{0b1011});
-- PORTB = PORTB & ???@as(u4, 1 << 2); // What character is missing here?
-+ PORTB = PORTB & ~@as(u4, 1 << 2);
- checkAnswer(0b1010, PORTB);
-
- newline();
-@@ -277,7 +277,7 @@
- PORTB = 0b0111; // reset PORTB
- print(" {b:0>4} // (reset state)\n", .{PORTB});
- print("& {b:0>4} // (bitmask)\n", .{0b1110});
-- PORTB &= ~(1 << 0); // What's missing here?
-+ PORTB &= ~@as(u4, 1 << 0);
- checkAnswer(0b0110, PORTB);
-
- newline();
diff --git a/patches/patches/111_labeled_switch.patch b/patches/patches/111_labeled_switch.patch
new file mode 100644
index 0000000..0fb872d
--- /dev/null
+++ b/patches/patches/111_labeled_switch.patch
@@ -0,0 +1,18 @@
+--- exercises/111_labeled_switch.zig 2025-08-15 15:17:57.840348083 +0200
++++ answers/111_labeled_switch.zig 2026-04-02 10:51:15.817831773 +0200
+@@ -67,13 +67,13 @@
+ // how would you fix it?
+ pr: switch (PullRequestState.Draft) {
+ PullRequestState.Draft => continue :pr PullRequestState.InReview,
+- PullRequestState.InReview => continue :pr PullRequestState.Rejected,
++ PullRequestState.InReview => continue :pr PullRequestState.Approved,
+ PullRequestState.Approved => continue :pr PullRequestState.Merged,
+ PullRequestState.Rejected => {
+ std.debug.print("The pull request has been rejected.\n", .{});
+ return;
+ },
+- PullRequestState.Merged => break, // Would you know where to break to?
++ PullRequestState.Merged => break :pr, // Would you know where to break to?
+ }
+ std.debug.print("The pull request has been merged.\n", .{});
+ }
diff --git a/patches/patches/111_packed.patch b/patches/patches/111_packed.patch
deleted file mode 100644
index d38ac68..0000000
--- a/patches/patches/111_packed.patch
+++ /dev/null
@@ -1,57 +0,0 @@
---- exercises/111_packed.zig 2026-03-13 11:18:44
-+++ answers/111_packed.zig 2026-03-13 11:18:57
-@@ -41,7 +41,7 @@
-
- const PackedStruct = packed struct {
- a: u2,
-- b: u?,
-+ b: u4,
- };
-
- comptime {
-@@ -50,7 +50,7 @@
-
- const PackedUnion = packed union {
- a: bool,
-- b: u?,
-+ b: u1,
- };
-
- comptime {
-@@ -113,31 +113,31 @@
- pub fn main() void {
- {
- const expected: Bits = @bitCast(@as(u4, 0b1000));
-- const my_bits: Bits = .{};
-+ const my_bits: Bits = .{ .d = 1 };
- if (my_bits != expected) complain(my_bits, expected, @src());
- }
-
- {
- const expected: Bits = @bitCast(@as(u4, 0b0001));
-- const my_bits: Bits = .{};
-+ const my_bits: Bits = .{ .a = 1 };
- if (my_bits != expected) complain(my_bits, expected, @src());
- }
-
- {
- const expected: Bits = @bitCast(@as(u4, 0b0010));
-- const my_bits: Bits = .{};
-+ const my_bits: Bits = .{ .b = 1 };
- if (my_bits != expected) complain(my_bits, expected, @src());
- }
-
- {
- const expected: Bits = @bitCast(@as(u4, 0b0011));
-- const my_bits: Bits = .{};
-+ const my_bits: Bits = .{ .a = 1, .b = 1 };
- if (my_bits != expected) complain(my_bits, expected, @src());
- }
-
- {
- const expected: Bits = @bitCast(@as(u4, 0b1101));
-- const my_bits: Bits = .{};
-+ const my_bits: Bits = .{ .a = 1, .c = 1, .d = 1 };
- if (my_bits != expected) complain(my_bits, expected, @src());
- }
- }
diff --git a/patches/patches/112_packed2.patch b/patches/patches/112_packed2.patch
deleted file mode 100644
index 9b01eb3..0000000
--- a/patches/patches/112_packed2.patch
+++ /dev/null
@@ -1,32 +0,0 @@
---- exercises/112_packed2.zig 2026-03-13 11:14:08
-+++ answers/112_packed2.zig 2026-03-13 11:14:16
-@@ -13,9 +13,9 @@
- const s: S = .{ .a = true, .b = -1 };
- switch (s) {
- .{ .a = true, .b = -1 } => {}, // ok!
-- .{ .a = true, .b = ??? },
-- .{ .a = ???, .b = 0 },
-- .{ .a = ???, .b = ??? },
-+ .{ .a = true, .b = 0 },
-+ .{ .a = false, .b = 0 },
-+ .{ .a = false, .b = -1 },
- => @compileError("We don't want to end up here!"),
- }
- }
-@@ -39,7 +39,6 @@
- .{ .a = 3 } => {}, // ok!
- .{ .a = 2 },
- .{ .b = 1 },
-- .{ .b = -1 },
- .{ .a = 0 },
- => @compileError("We don't want to end up here!"),
- }
-@@ -65,7 +64,7 @@
- // Reminder: if the sign bit of a float is set, the number is negative!
-
- var number: Float = .{ .value = 2.34 };
-- number.bits.??? = ???;
-+ number.bits.sign = 1;
- if (number.value != -2.34) {
- std.debug.print("Make it negative!\n", .{});
- }
diff --git a/patches/patches/112_vectors.patch b/patches/patches/112_vectors.patch
new file mode 100644
index 0000000..5bf684b
--- /dev/null
+++ b/patches/patches/112_vectors.patch
@@ -0,0 +1,13 @@
+--- exercises/112_vectors.zig 2025-08-15 15:17:57.840348083 +0200
++++ answers/112_vectors.zig 2026-04-02 10:51:15.819831812 +0200
+@@ -121,8 +121,8 @@
+
+ const Vec4 = @Vector(4, f32);
+ fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 {
+- const abs_diff_vec = ???;
+- const max_diff = @reduce(???, abs_diff_vec);
++ const abs_diff_vec = @abs(a - b);
++ const max_diff = @reduce(.Max, abs_diff_vec);
+ return max_diff;
+ }
+
diff --git a/patches/patches/113_quiz9.patch b/patches/patches/113_quiz9.patch
new file mode 100644
index 0000000..03bd572
--- /dev/null
+++ b/patches/patches/113_quiz9.patch
@@ -0,0 +1,56 @@
+--- exercises/113_quiz9.zig 2025-08-15 15:17:57.840348083 +0200
++++ answers/113_quiz9.zig 2026-04-02 10:51:15.821831851 +0200
+@@ -84,7 +84,7 @@
+ PORTB = 0b1100;
+ print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
+ print("^ {b:0>4} // (bitmask)\n", .{0b0101});
+- PORTB ^= (1 << 1) | (1 << 0); // What's wrong here?
++ PORTB ^= (1 << 2) | (1 << 0);
+ checkAnswer(0b1001, PORTB);
+
+ newline();
+@@ -92,7 +92,7 @@
+ PORTB = 0b1100;
+ print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
+ print("^ {b:0>4} // (bitmask)\n", .{0b0011});
+- PORTB ^= (1 << 1) & (1 << 0); // What's wrong here?
++ PORTB ^= (1 << 1) | (1 << 0);
+ checkAnswer(0b1111, PORTB);
+
+ newline();
+@@ -103,7 +103,7 @@
+ PORTB = 0b1001; // reset PORTB
+ print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
+ print("| {b:0>4} // (bitmask)\n", .{0b0100});
+- PORTB = PORTB ??? (1 << 2); // What's missing here?
++ PORTB = PORTB | (1 << 2);
+ checkAnswer(0b1101, PORTB);
+
+ newline();
+@@ -111,7 +111,7 @@
+ PORTB = 0b1001; // reset PORTB
+ print(" {b:0>4} // (reset state)\n", .{PORTB});
+ print("| {b:0>4} // (bitmask)\n", .{0b0100});
+- PORTB ??? (1 << 2); // What's missing here?
++ PORTB |= (1 << 2);
+ checkAnswer(0b1101, PORTB);
+
+ newline();
+@@ -122,7 +122,7 @@
+ PORTB = 0b1110; // reset PORTB
+ print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
+ print("& {b:0>4} // (bitmask)\n", .{0b1011});
+- PORTB = PORTB & ???@as(u4, 1 << 2); // What character is missing here?
++ PORTB = PORTB & ~@as(u4, 1 << 2);
+ checkAnswer(0b1010, PORTB);
+
+ newline();
+@@ -130,7 +130,7 @@
+ PORTB = 0b0111; // reset PORTB
+ print(" {b:0>4} // (reset state)\n", .{PORTB});
+ print("& {b:0>4} // (bitmask)\n", .{0b1110});
+- PORTB &= ~(1 << 0); // What's missing here?
++ PORTB &= ~@as(u4, 1 << 0);
+ checkAnswer(0b0110, PORTB);
+
+ newline();
diff --git a/patches/patches/114_packed.patch b/patches/patches/114_packed.patch
new file mode 100644
index 0000000..3c79f18
--- /dev/null
+++ b/patches/patches/114_packed.patch
@@ -0,0 +1,57 @@
+--- exercises/114_packed.zig 2026-03-20 19:23:48.874150121 +0100
++++ answers/114_packed.zig 2026-04-02 10:51:15.824831910 +0200
+@@ -41,7 +41,7 @@
+
+ const PackedStruct = packed struct {
+ a: u2,
+- b: u?,
++ b: u4,
+ };
+
+ comptime {
+@@ -50,7 +50,7 @@
+
+ const PackedUnion = packed union {
+ a: bool,
+- b: u?,
++ b: u1,
+ };
+
+ comptime {
+@@ -113,31 +113,31 @@
+ pub fn main() void {
+ {
+ const expected: Bits = @bitCast(@as(u4, 0b1000));
+- const my_bits: Bits = .{};
++ const my_bits: Bits = .{ .d = 1 };
+ if (my_bits != expected) complain(my_bits, expected, @src());
+ }
+
+ {
+ const expected: Bits = @bitCast(@as(u4, 0b0001));
+- const my_bits: Bits = .{};
++ const my_bits: Bits = .{ .a = 1 };
+ if (my_bits != expected) complain(my_bits, expected, @src());
+ }
+
+ {
+ const expected: Bits = @bitCast(@as(u4, 0b0010));
+- const my_bits: Bits = .{};
++ const my_bits: Bits = .{ .b = 1 };
+ if (my_bits != expected) complain(my_bits, expected, @src());
+ }
+
+ {
+ const expected: Bits = @bitCast(@as(u4, 0b0011));
+- const my_bits: Bits = .{};
++ const my_bits: Bits = .{ .a = 1, .b = 1 };
+ if (my_bits != expected) complain(my_bits, expected, @src());
+ }
+
+ {
+ const expected: Bits = @bitCast(@as(u4, 0b1101));
+- const my_bits: Bits = .{};
++ const my_bits: Bits = .{ .a = 1, .c = 1, .d = 1 };
+ if (my_bits != expected) complain(my_bits, expected, @src());
+ }
+ }
diff --git a/patches/patches/115_packed2.patch b/patches/patches/115_packed2.patch
new file mode 100644
index 0000000..81c32ab
--- /dev/null
+++ b/patches/patches/115_packed2.patch
@@ -0,0 +1,32 @@
+--- exercises/115_packed2.zig 2026-03-20 19:23:48.874150121 +0100
++++ answers/115_packed2.zig 2026-04-02 10:51:15.826831949 +0200
+@@ -13,9 +13,9 @@
+ const s: S = .{ .a = true, .b = -1 };
+ switch (s) {
+ .{ .a = true, .b = -1 } => {}, // ok!
+- .{ .a = true, .b = ??? },
+- .{ .a = ???, .b = 0 },
+- .{ .a = ???, .b = ??? },
++ .{ .a = true, .b = 0 },
++ .{ .a = false, .b = 0 },
++ .{ .a = false, .b = -1 },
+ => @compileError("We don't want to end up here!"),
+ }
+ }
+@@ -39,7 +39,6 @@
+ .{ .a = 3 } => {}, // ok!
+ .{ .a = 2 },
+ .{ .b = 1 },
+- .{ .b = -1 },
+ .{ .a = 0 },
+ => @compileError("We don't want to end up here!"),
+ }
+@@ -65,7 +64,7 @@
+ // Reminder: if the sign bit of a float is set, the number is negative!
+
+ var number: Float = .{ .value = 2.34 };
+- number.bits.??? = ???;
++ number.bits.sign = 1;
+ if (number.value != -2.34) {
+ std.debug.print("Make it negative!\n", .{});
+ }
--
cgit v1.2.3
From 25009361533be5e45cf59d9840edf5d13cfb8d6d Mon Sep 17 00:00:00 2001
From: Chris Boesch
Date: Fri, 3 Apr 2026 14:28:19 +0200
Subject: new async exercise
---
build.zig | 15 ++++++---
exercises/093_async10.zig | 67 +++++++++++++++++++++++++++++++++++++++
patches/patches/093_async10.patch | 13 ++++++++
3 files changed, 91 insertions(+), 4 deletions(-)
create mode 100644 exercises/093_async10.zig
create mode 100644 patches/patches/093_async10.patch
(limited to 'build.zig')
diff --git a/build.zig b/build.zig
index 5e5d3de..b9aaac3 100644
--- a/build.zig
+++ b/build.zig
@@ -1161,13 +1161,20 @@ const exercises = [_]Exercise{
},
.{
.main_file = "092_async9.zig",
- .output = "",
- .skip = true,
+ .output =
+ \\Main thread continues...
+ \\Computing on a separate thread!
+ \\Main thread done waiting.
+ \\Result: 123
+ , // pay attention to the comma
},
.{
.main_file = "093_async10.zig",
- .output = "",
- .skip = true,
+ .output =
+ \\Starting critical section...
+ \\Critical section completed safely.
+ \\Task result: All data saved.
+ , // pay attention to the comma
},
.{
.main_file = "094_async_quiz.zig",
diff --git a/exercises/093_async10.zig b/exercises/093_async10.zig
new file mode 100644
index 0000000..6ed229d
--- /dev/null
+++ b/exercises/093_async10.zig
@@ -0,0 +1,67 @@
+//
+// In exercise 088, 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});
+
+ // Give the task time to start and enter its critical section.
+ io.sleep(std.Io.Duration.fromMilliseconds(300), .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(600), .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/patches/patches/093_async10.patch b/patches/patches/093_async10.patch
new file mode 100644
index 0000000..69f7518
--- /dev/null
+++ b/patches/patches/093_async10.patch
@@ -0,0 +1,13 @@
+--- exercises/093_async10.zig 2026-04-03 14:25:16.600025924 +0200
++++ answers/093_async10.zig 2026-04-03 14:24:56.192615893 +0200
+@@ -50,8 +50,8 @@
+
+ // Protect this section from cancellation.
+ // What method swaps the cancel protection state?
+- const old = io.???(. blocked);
+- defer _ = io.???(old);
++ const old = io.swapCancelProtection(.blocked);
++ defer _ = io.swapCancelProtection(old);
+
+ // This sleep will NOT return error.Canceled even though
+ // we get canceled during it — protection is active!
--
cgit v1.2.3
From 1c6487c1e79cbe0d59a39b483af8ec44b59c586e Mon Sep 17 00:00:00 2001
From: Chris Boesch
Date: Fri, 3 Apr 2026 18:11:00 +0200
Subject: added async-io quiz
---
build.zig | 46 +++++----
exercises/084_async.zig | 48 ---------
exercises/084_interfaces.zig | 127 ++++++++++++++++++++++++
exercises/085_async.zig | 48 +++++++++
exercises/085_async2.zig | 48 ---------
exercises/086_async2.zig | 48 +++++++++
exercises/086_async3.zig | 50 ----------
exercises/087_async3.zig | 50 ++++++++++
exercises/087_async4.zig | 50 ----------
exercises/088_async4.zig | 50 ++++++++++
exercises/088_async5.zig | 61 ------------
exercises/089_async5.zig | 61 ++++++++++++
exercises/089_async6.zig | 71 -------------
exercises/090_async6.zig | 71 +++++++++++++
exercises/090_async7.zig | 57 -----------
exercises/091_async7.zig | 57 +++++++++++
exercises/091_async8.zig | 62 ------------
exercises/092_async8.zig | 62 ++++++++++++
exercises/092_async9.zig | 57 -----------
exercises/093_async10.zig | 67 -------------
exercises/093_async9.zig | 57 +++++++++++
exercises/094_async10.zig | 67 +++++++++++++
exercises/095_interfaces.zig | 127 ------------------------
exercises/095_quiz_async.zig | 186 +++++++++++++++++++++++++++++++++++
patches/patches/084_interfaces.patch | 11 +++
patches/patches/085_async.patch | 11 +++
patches/patches/086_async2.patch | 14 +++
patches/patches/087_async3.patch | 18 ++++
patches/patches/088_async4.patch | 11 +++
patches/patches/089_async5.patch | 11 +++
patches/patches/090_async6.patch | 11 +++
patches/patches/091_async7.patch | 13 +++
patches/patches/092_async8.patch | 11 +++
patches/patches/093_async9.patch | 11 +++
patches/patches/094_async10.patch | 13 +++
patches/patches/095_quiz_async.patch | 52 ++++++++++
36 files changed, 1097 insertions(+), 718 deletions(-)
delete mode 100644 exercises/084_async.zig
create mode 100644 exercises/084_interfaces.zig
create mode 100644 exercises/085_async.zig
delete mode 100644 exercises/085_async2.zig
create mode 100644 exercises/086_async2.zig
delete mode 100644 exercises/086_async3.zig
create mode 100644 exercises/087_async3.zig
delete mode 100644 exercises/087_async4.zig
create mode 100644 exercises/088_async4.zig
delete mode 100644 exercises/088_async5.zig
create mode 100644 exercises/089_async5.zig
delete mode 100644 exercises/089_async6.zig
create mode 100644 exercises/090_async6.zig
delete mode 100644 exercises/090_async7.zig
create mode 100644 exercises/091_async7.zig
delete mode 100644 exercises/091_async8.zig
create mode 100644 exercises/092_async8.zig
delete mode 100644 exercises/092_async9.zig
delete mode 100644 exercises/093_async10.zig
create mode 100644 exercises/093_async9.zig
create mode 100644 exercises/094_async10.zig
delete mode 100644 exercises/095_interfaces.zig
create mode 100644 exercises/095_quiz_async.zig
create mode 100644 patches/patches/084_interfaces.patch
create mode 100644 patches/patches/085_async.patch
create mode 100644 patches/patches/086_async2.patch
create mode 100644 patches/patches/087_async3.patch
create mode 100644 patches/patches/088_async4.patch
create mode 100644 patches/patches/089_async5.patch
create mode 100644 patches/patches/090_async6.patch
create mode 100644 patches/patches/091_async7.patch
create mode 100644 patches/patches/092_async8.patch
create mode 100644 patches/patches/093_async9.patch
create mode 100644 patches/patches/094_async10.patch
create mode 100644 patches/patches/095_quiz_async.patch
(limited to 'build.zig')
diff --git a/build.zig b/build.zig
index b9aaac3..beb0d72 100644
--- a/build.zig
+++ b/build.zig
@@ -1109,20 +1109,29 @@ const exercises = [_]Exercise{
.main_file = "083_anonymous_lists.zig",
.output = "I say hello!",
},
+ .{
+ .main_file = "084_interfaces.zig",
+ .output =
+ \\Daily Insect Report:
+ \\Ant is alive.
+ \\Bee visited 17 flowers.
+ \\Grasshopper hopped 32 meters.
+ , // pay attention to the comma
+ },
// Skipped because of https://github.com/ratfactor/ziglings/issues/163
// direct link: https://github.com/ziglang/zig/issues/6025
.{
- .main_file = "084_async.zig",
+ .main_file = "085_async.zig",
.output = "Current time: s since epoch",
.timestamp = true,
},
.{
- .main_file = "085_async2.zig",
+ .main_file = "086_async2.zig",
.output = "Computing... the answer is: 42",
},
.{
- .main_file = "086_async3.zig",
+ .main_file = "087_async3.zig",
.output =
\\1 + 2 = 3
\\6 * 7 = 42
@@ -1130,7 +1139,7 @@ const exercises = [_]Exercise{
, // pay attention to the comma
},
.{
- .main_file = "087_async4.zig",
+ .main_file = "088_async4.zig",
.output =
\\Task 1 done.
\\Task 2 done.
@@ -1139,7 +1148,7 @@ const exercises = [_]Exercise{
, // pay attention to the comma
},
.{
- .main_file = "088_async5.zig",
+ .main_file = "089_async5.zig",
.output =
\\Starting long computation...
\\Canceling slow task...
@@ -1148,19 +1157,19 @@ const exercises = [_]Exercise{
, // pay attention to the comma
},
.{
- .main_file = "089_async6.zig",
+ .main_file = "090_async6.zig",
.output = "Hare: I'm fast!",
},
.{
- .main_file = "090_async7.zig",
+ .main_file = "091_async7.zig",
.output = "Counter: 400",
},
.{
- .main_file = "091_async8.zig",
+ .main_file = "092_async8.zig",
.output = "Sum of 1..10 = 55",
},
.{
- .main_file = "092_async9.zig",
+ .main_file = "093_async9.zig",
.output =
\\Main thread continues...
\\Computing on a separate thread!
@@ -1169,7 +1178,7 @@ const exercises = [_]Exercise{
, // pay attention to the comma
},
.{
- .main_file = "093_async10.zig",
+ .main_file = "094_async10.zig",
.output =
\\Starting critical section...
\\Critical section completed safely.
@@ -1177,17 +1186,14 @@ const exercises = [_]Exercise{
, // pay attention to the comma
},
.{
- .main_file = "094_async_quiz.zig",
- .output = "",
- .skip = true,
- },
- .{
- .main_file = "095_interfaces.zig",
+ .main_file = "095_quiz_async.zig",
.output =
- \\Daily Insect Report:
- \\Ant is alive.
- \\Bee visited 17 flowers.
- \\Grasshopper hopped 32 meters.
+ \\=== Doctor Zoraptera's Garden Report ===
+ \\Temperature : 23C
+ \\Humidity : 63%
+ \\Wind : 13 km/h
+ \\Readings : 9
+ \\Bee-friendly conditions! Expect high pollination.
, // pay attention to the comma
},
.{
diff --git a/exercises/084_async.zig b/exercises/084_async.zig
deleted file mode 100644
index 48bda2b..0000000
--- a/exercises/084_async.zig
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-// 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 - classic thread-pool based I/O
-// * Uring - Linux io_uring
-// * Kqueue - BSD/macOS
-// * Dispatch - 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/084_interfaces.zig b/exercises/084_interfaces.zig
new file mode 100644
index 0000000..7775dd5
--- /dev/null
+++ b/exercises/084_interfaces.zig
@@ -0,0 +1,127 @@
+//
+// Remember our ant and bee simulator constructed with unions
+// back in exercises 55 and 56? There, we demonstrated that
+// unions allow us to treat different data types in a uniform
+// manner.
+//
+// One neat feature was using tagged unions to create a single
+// function to print a status for ants *or* bees by switching:
+//
+// switch (insect) {
+// .still_alive => ... // (print ant stuff)
+// .flowers_visited => ... // (print bee stuff)
+// }
+//
+// Well, that simulation was running just fine until a new insect
+// arrived in the virtual garden, a grasshopper!
+//
+// Doctor Zoraptera started to add grasshopper code to the
+// program, but then she backed away from her keyboard with an
+// angry hissing sound. She had realized that having code for
+// each insect in one place and code to print each insect in
+// another place was going to become unpleasant to maintain when
+// the simulation expanded to hundreds of different insects.
+//
+// Thankfully, Zig has another comptime feature we can use
+// to get out of this dilemma called the 'inline else'.
+//
+// We can replace this redundant code:
+//
+// switch (thing) {
+// .a => |a| special(a),
+// .b => |b| normal(b),
+// .c => |c| normal(c),
+// .d => |d| normal(d),
+// .e => |e| normal(e),
+// ...
+// }
+//
+// With:
+//
+// switch (thing) {
+// .a => |a| special(a),
+// inline else => |t| normal(t),
+// }
+//
+// We can have special handling of some cases and then Zig
+// handles the rest of the matches for us.
+//
+// With this feature, you decide to make an Insect union with a
+// single uniform 'print()' function. All of the insects can
+// then be responsible for printing themselves. And Doctor
+// Zoraptera can calm down and stop gnawing on the furniture.
+//
+const std = @import("std");
+
+const Ant = struct {
+ still_alive: bool,
+
+ pub fn print(self: Ant) void {
+ std.debug.print("Ant is {s}.\n", .{if (self.still_alive) "alive" else "dead"});
+ }
+};
+
+const Bee = struct {
+ flowers_visited: u16,
+
+ pub fn print(self: Bee) void {
+ std.debug.print("Bee visited {} flowers.\n", .{self.flowers_visited});
+ }
+};
+
+// Here's the new grasshopper. Notice how we've also added print
+// methods to each insect.
+const Grasshopper = struct {
+ distance_hopped: u16,
+
+ pub fn print(self: Grasshopper) void {
+ std.debug.print("Grasshopper hopped {} meters.\n", .{self.distance_hopped});
+ }
+};
+
+const Insect = union(enum) {
+ ant: Ant,
+ bee: Bee,
+ grasshopper: Grasshopper,
+
+ // Thanks to 'inline else', we can think of this print() as
+ // being an interface method. Any member of this union with
+ // a print() method can be treated uniformly by outside
+ // code without needing to know any other details. Cool!
+ pub fn print(self: Insect) void {
+ switch (self) {
+ inline else => |case| return case.print(),
+ }
+ }
+};
+
+pub fn main() !void {
+ const my_insects = [_]Insect{
+ Insect{ .ant = Ant{ .still_alive = true } },
+ Insect{ .bee = Bee{ .flowers_visited = 17 } },
+ Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } },
+ };
+
+ std.debug.print("Daily Insect Report:\n", .{});
+ for (my_insects) |insect| {
+ // Almost done! We want to print() each insect with a
+ // single method call here.
+ ???
+ }
+}
+
+// Our print() method in the Insect union above demonstrates
+// something very similar to the object-oriented concept of an
+// abstract data type. That is, the Insect type doesn't contain
+// the underlying data, and the print() function doesn't
+// actually do the printing.
+//
+// The point of an interface is to support generic programming:
+// the ability to treat different things as if they were the
+// same to cut down on clutter and conceptual complexity.
+//
+// The Daily Insect Report doesn't need to worry about *which*
+// insects are in the report - they all print the same way via
+// the interface!
+//
+// Doctor Zoraptera loves it.
diff --git a/exercises/085_async.zig b/exercises/085_async.zig
new file mode 100644
index 0000000..48bda2b
--- /dev/null
+++ b/exercises/085_async.zig
@@ -0,0 +1,48 @@
+//
+// 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 - classic thread-pool based I/O
+// * Uring - Linux io_uring
+// * Kqueue - BSD/macOS
+// * Dispatch - 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 1f1c4c8..0000000
--- a/exercises/085_async2.zig
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-// 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 });
-// // ... do other work here ...
-// 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. (In the Threaded backend, if no thread is
-// available, the function runs synchronously right away and
-// .await() just returns the already-computed result.)
-//
-// io.async() returns a Future(T) where T is the return type
-// of the function you passed in. Future has two key methods:
-//
-// .await(io) - block until the result is ready, return it
-// .cancel(io) - request cancellation, then return the result
-//
-// Fix this program so that computeAnswer runs asynchronously
-// and its result is properly awaited.
-//
-const std = @import("std");
-
-pub fn main(init: std.process.Init) !void {
- const io = init.io;
-
- // Launch computeAnswer asynchronously.
- // io.async() takes a function and a tuple of its arguments.
- var future = io.async(computeAnswer, .{ 6, 7 });
-
- // Meanwhile, print something to show we're not blocked.
- std.debug.print("Computing... ", .{});
-
- // Now collect the result. What method on Future gives us
- // the value, blocking if it isn't ready yet?
- const answer = future.???(io);
-
- std.debug.print("The answer is: {}\n", .{answer});
-}
-
-fn computeAnswer(a: u32, b: u32) u32 {
- return a * b;
-}
diff --git a/exercises/086_async2.zig b/exercises/086_async2.zig
new file mode 100644
index 0000000..1f1c4c8
--- /dev/null
+++ b/exercises/086_async2.zig
@@ -0,0 +1,48 @@
+//
+// 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 });
+// // ... do other work here ...
+// 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. (In the Threaded backend, if no thread is
+// available, the function runs synchronously right away and
+// .await() just returns the already-computed result.)
+//
+// io.async() returns a Future(T) where T is the return type
+// of the function you passed in. Future has two key methods:
+//
+// .await(io) - block until the result is ready, return it
+// .cancel(io) - request cancellation, then return the result
+//
+// Fix this program so that computeAnswer runs asynchronously
+// and its result is properly awaited.
+//
+const std = @import("std");
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+
+ // Launch computeAnswer asynchronously.
+ // io.async() takes a function and a tuple of its arguments.
+ var future = io.async(computeAnswer, .{ 6, 7 });
+
+ // Meanwhile, print something to show we're not blocked.
+ std.debug.print("Computing... ", .{});
+
+ // Now collect the result. What method on Future gives us
+ // the value, blocking if it isn't ready yet?
+ const answer = future.???(io);
+
+ std.debug.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 07221e9..0000000
--- a/exercises/086_async3.zig
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-// 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, .{});
-// var f2 = io.async(taskB, .{});
-//
-// // Both tasks may be running now!
-// const a = f1.await(io);
-// const b = f2.await(io);
-//
-// There's also io.concurrent() which provides a STRONGER guarantee:
-// it ensures the function gets its own unit of concurrency (e.g. a
-// real OS thread). But it can fail with error.ConcurrencyUnavailable
-// if resources are exhausted.
-//
-// io.async() is more portable: if no thread is available, it simply
-// runs the function synchronously. This makes it the right default
-// for most code.
-//
-// 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, .{ 10, 20 });
- var future_b = ???(slowMul, .{ 6, 7 });
-
- // Await both results.
- const sum = future_a.await(io);
- const product = future_b.???(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_async3.zig b/exercises/087_async3.zig
new file mode 100644
index 0000000..07221e9
--- /dev/null
+++ b/exercises/087_async3.zig
@@ -0,0 +1,50 @@
+//
+// 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, .{});
+// var f2 = io.async(taskB, .{});
+//
+// // Both tasks may be running now!
+// const a = f1.await(io);
+// const b = f2.await(io);
+//
+// There's also io.concurrent() which provides a STRONGER guarantee:
+// it ensures the function gets its own unit of concurrency (e.g. a
+// real OS thread). But it can fail with error.ConcurrencyUnavailable
+// if resources are exhausted.
+//
+// io.async() is more portable: if no thread is available, it simply
+// runs the function synchronously. This makes it the right default
+// for most code.
+//
+// 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, .{ 10, 20 });
+ var future_b = ???(slowMul, .{ 6, 7 });
+
+ // Await both results.
+ const sum = future_a.await(io);
+ const product = future_b.???(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 50829fc..0000000
--- a/exercises/087_async4.zig
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-// 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 waits for them to 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.???
-
- 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_async4.zig b/exercises/088_async4.zig
new file mode 100644
index 0000000..50829fc
--- /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 waits for them to 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.???
+
+ 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 4fb8d76..0000000
--- a/exercises/088_async5.zig
+++ /dev/null
@@ -1,61 +0,0 @@
-//
-// 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. Waits for the task to actually finish
-// 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, // handle gracefully
-// };
-// return 42;
-// }
-//
-// This is fundamentally different from killing a thread -
-// the task gets a chance to clean up and return a value!
-//
-// 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});
-
- // 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 = ???;
-
- 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_async5.zig b/exercises/089_async5.zig
new file mode 100644
index 0000000..4fb8d76
--- /dev/null
+++ b/exercises/089_async5.zig
@@ -0,0 +1,61 @@
+//
+// 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. Waits for the task to actually finish
+// 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, // handle gracefully
+// };
+// return 42;
+// }
+//
+// This is fundamentally different from killing a thread -
+// the task gets a chance to clean up and return a value!
+//
+// 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});
+
+ // 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 = ???;
+
+ 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 eab03c9..0000000
--- a/exercises/089_async6.zig
+++ /dev/null
@@ -1,71 +0,0 @@
-//
-// 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
-//
-// 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 = ???;
-
- 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_async6.zig b/exercises/090_async6.zig
new file mode 100644
index 0000000..eab03c9
--- /dev/null
+++ b/exercises/090_async6.zig
@@ -0,0 +1,71 @@
+//
+// 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
+//
+// 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 = ???;
+
+ 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 bfe6ffd..0000000
--- a/exercises/090_async7.zig
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// 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_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 10921c3..0000000
--- a/exercises/091_async8.zig
+++ /dev/null
@@ -1,62 +0,0 @@
-//
-// 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/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/092_async9.zig b/exercises/092_async9.zig
deleted file mode 100644
index ad30dcf..0000000
--- a/exercises/092_async9.zig
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// 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 another thread, or it may run
-// immediately on the current thread (synchronously).
-// * Never fails — if no thread is available, it just runs
-// the function right away.
-// * More portable, works with all Io backends.
-//
-// io.concurrent():
-// * GUARANTEES a separate unit of concurrency (a real thread
-// in the Threaded backend).
-// * Can fail with error.ConcurrencyUnavailable if resources
-// are exhausted or the backend doesn't support it.
-// * Use when you NEED true parallelism.
-//
-// Because concurrent() can fail, you must handle the error:
-//
-// var future = try io.concurrent(myFn, .{args});
-// 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 thread.
- // Which Io method guarantees true concurrency?
- // (Hint: unlike io.async, this one can fail!)
- var future = try io.???(compute, .{io});
-
- print("Main thread continues...\n", .{});
-
- // Wait 100 millisecond so the output order is deterministic.
- io.sleep(std.Io.Duration.fromMilliseconds(100), .awake) catch {};
-
- print("Main thread done waiting.\n", .{});
-
- const result = future.await(io);
- print("Result: {}\n", .{result});
-}
-
-fn compute(io: std.Io) u32 {
- print("Computing on a separate thread!\n", .{});
- // Simulate some work.
- io.sleep(std.Io.Duration.fromMilliseconds(200), .awake) catch return 0;
- return 123;
-}
diff --git a/exercises/093_async10.zig b/exercises/093_async10.zig
deleted file mode 100644
index 6ed229d..0000000
--- a/exercises/093_async10.zig
+++ /dev/null
@@ -1,67 +0,0 @@
-//
-// In exercise 088, 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});
-
- // Give the task time to start and enter its critical section.
- io.sleep(std.Io.Duration.fromMilliseconds(300), .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(600), .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/093_async9.zig b/exercises/093_async9.zig
new file mode 100644
index 0000000..ad30dcf
--- /dev/null
+++ b/exercises/093_async9.zig
@@ -0,0 +1,57 @@
+//
+// 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 another thread, or it may run
+// immediately on the current thread (synchronously).
+// * Never fails — if no thread is available, it just runs
+// the function right away.
+// * More portable, works with all Io backends.
+//
+// io.concurrent():
+// * GUARANTEES a separate unit of concurrency (a real thread
+// in the Threaded backend).
+// * Can fail with error.ConcurrencyUnavailable if resources
+// are exhausted or the backend doesn't support it.
+// * Use when you NEED true parallelism.
+//
+// Because concurrent() can fail, you must handle the error:
+//
+// var future = try io.concurrent(myFn, .{args});
+// 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 thread.
+ // Which Io method guarantees true concurrency?
+ // (Hint: unlike io.async, this one can fail!)
+ var future = try io.???(compute, .{io});
+
+ print("Main thread continues...\n", .{});
+
+ // Wait 100 millisecond so the output order is deterministic.
+ io.sleep(std.Io.Duration.fromMilliseconds(100), .awake) catch {};
+
+ print("Main thread done waiting.\n", .{});
+
+ const result = future.await(io);
+ print("Result: {}\n", .{result});
+}
+
+fn compute(io: std.Io) u32 {
+ print("Computing on a separate thread!\n", .{});
+ // Simulate some work.
+ io.sleep(std.Io.Duration.fromMilliseconds(200), .awake) catch return 0;
+ return 123;
+}
diff --git a/exercises/094_async10.zig b/exercises/094_async10.zig
new file mode 100644
index 0000000..6ed229d
--- /dev/null
+++ b/exercises/094_async10.zig
@@ -0,0 +1,67 @@
+//
+// In exercise 088, 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});
+
+ // Give the task time to start and enter its critical section.
+ io.sleep(std.Io.Duration.fromMilliseconds(300), .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(600), .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_interfaces.zig b/exercises/095_interfaces.zig
deleted file mode 100644
index 7775dd5..0000000
--- a/exercises/095_interfaces.zig
+++ /dev/null
@@ -1,127 +0,0 @@
-//
-// Remember our ant and bee simulator constructed with unions
-// back in exercises 55 and 56? There, we demonstrated that
-// unions allow us to treat different data types in a uniform
-// manner.
-//
-// One neat feature was using tagged unions to create a single
-// function to print a status for ants *or* bees by switching:
-//
-// switch (insect) {
-// .still_alive => ... // (print ant stuff)
-// .flowers_visited => ... // (print bee stuff)
-// }
-//
-// Well, that simulation was running just fine until a new insect
-// arrived in the virtual garden, a grasshopper!
-//
-// Doctor Zoraptera started to add grasshopper code to the
-// program, but then she backed away from her keyboard with an
-// angry hissing sound. She had realized that having code for
-// each insect in one place and code to print each insect in
-// another place was going to become unpleasant to maintain when
-// the simulation expanded to hundreds of different insects.
-//
-// Thankfully, Zig has another comptime feature we can use
-// to get out of this dilemma called the 'inline else'.
-//
-// We can replace this redundant code:
-//
-// switch (thing) {
-// .a => |a| special(a),
-// .b => |b| normal(b),
-// .c => |c| normal(c),
-// .d => |d| normal(d),
-// .e => |e| normal(e),
-// ...
-// }
-//
-// With:
-//
-// switch (thing) {
-// .a => |a| special(a),
-// inline else => |t| normal(t),
-// }
-//
-// We can have special handling of some cases and then Zig
-// handles the rest of the matches for us.
-//
-// With this feature, you decide to make an Insect union with a
-// single uniform 'print()' function. All of the insects can
-// then be responsible for printing themselves. And Doctor
-// Zoraptera can calm down and stop gnawing on the furniture.
-//
-const std = @import("std");
-
-const Ant = struct {
- still_alive: bool,
-
- pub fn print(self: Ant) void {
- std.debug.print("Ant is {s}.\n", .{if (self.still_alive) "alive" else "dead"});
- }
-};
-
-const Bee = struct {
- flowers_visited: u16,
-
- pub fn print(self: Bee) void {
- std.debug.print("Bee visited {} flowers.\n", .{self.flowers_visited});
- }
-};
-
-// Here's the new grasshopper. Notice how we've also added print
-// methods to each insect.
-const Grasshopper = struct {
- distance_hopped: u16,
-
- pub fn print(self: Grasshopper) void {
- std.debug.print("Grasshopper hopped {} meters.\n", .{self.distance_hopped});
- }
-};
-
-const Insect = union(enum) {
- ant: Ant,
- bee: Bee,
- grasshopper: Grasshopper,
-
- // Thanks to 'inline else', we can think of this print() as
- // being an interface method. Any member of this union with
- // a print() method can be treated uniformly by outside
- // code without needing to know any other details. Cool!
- pub fn print(self: Insect) void {
- switch (self) {
- inline else => |case| return case.print(),
- }
- }
-};
-
-pub fn main() !void {
- const my_insects = [_]Insect{
- Insect{ .ant = Ant{ .still_alive = true } },
- Insect{ .bee = Bee{ .flowers_visited = 17 } },
- Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } },
- };
-
- std.debug.print("Daily Insect Report:\n", .{});
- for (my_insects) |insect| {
- // Almost done! We want to print() each insect with a
- // single method call here.
- ???
- }
-}
-
-// Our print() method in the Insect union above demonstrates
-// something very similar to the object-oriented concept of an
-// abstract data type. That is, the Insect type doesn't contain
-// the underlying data, and the print() function doesn't
-// actually do the printing.
-//
-// The point of an interface is to support generic programming:
-// the ability to treat different things as if they were the
-// same to cut down on clutter and conceptual complexity.
-//
-// The Daily Insect Report doesn't need to worry about *which*
-// insects are in the report - they all print the same way via
-// the interface!
-//
-// Doctor Zoraptera loves it.
diff --git a/exercises/095_quiz_async.zig b/exercises/095_quiz_async.zig
new file mode 100644
index 0000000..fb78e7b
--- /dev/null
+++ b/exercises/095_quiz_async.zig
@@ -0,0 +1,186 @@
+//
+// 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 run concurrently, each sending
+// exactly 3 readings through a Queue
+// 2. A collector task receives readings, 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 084-093. *
+// * 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.lock(io) catch return;
+ self.mutex.???(io) catch return;
+
+ 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);
+
+ // Sensor group: runs all three sensors to completion.
+ var sensors: std.Io.Group = .init;
+
+ // Start three sensor tasks. They need GUARANTEED concurrency
+ // since they each simulate real-time measurement.
+ //
+ // Bug 2: io.async doesn't guarantee a separate thread.
+ // Which Io method guarantees true concurrency?
+ // (Don't forget: it can fail, so you need 'try'!)
+ try sensors.???(io, sensor, .{ io, &queue, .thermometer, 20 });
+ try sensors.???(io, sensor, .{ io, &queue, .hygrometer, 60 });
+ try sensors.???(io, sensor, .{ io, &queue, .anemometer, 10 });
+
+ // Collector group: processes readings from the queue.
+ var collectors: std.Io.Group = .init;
+ collectors.async(io, collector, .{ io, &queue, &weather });
+
+ // Bug 3: Wait for ALL sensors to finish sending their readings.
+ // What Group method blocks until all tasks complete?
+ try sensors.await(io);
+ // 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 queue.
+ try collectors.await(io);
+
+ // Now write the garden report. This is critical — it must
+ // NOT be interrupted, even if something tries to cancel us!
+ //
+ // Bug 4: 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 5: 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() - launch with guaranteed parallelism
+// Group.concurrent() - concurrent tasks in a group
+// 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 (Threaded, Uring,
+// Kqueue, Dispatch) without any changes!
+//
+// Doctor Zoraptera approves.
diff --git a/patches/patches/084_interfaces.patch b/patches/patches/084_interfaces.patch
new file mode 100644
index 0000000..a1d0628
--- /dev/null
+++ b/patches/patches/084_interfaces.patch
@@ -0,0 +1,11 @@
+--- exercises/084_interfaces.zig 2025-08-15 15:17:57.839348063 +0200
++++ answers/084_interfaces.zig 2026-04-03 14:27:32.670756488 +0200
+@@ -106,7 +106,7 @@
+ for (my_insects) |insect| {
+ // Almost done! We want to print() each insect with a
+ // single method call here.
+- ???
++ insect.print();
+ }
+ }
+
diff --git a/patches/patches/085_async.patch b/patches/patches/085_async.patch
new file mode 100644
index 0000000..ca8b102
--- /dev/null
+++ b/patches/patches/085_async.patch
@@ -0,0 +1,11 @@
+--- exercises/085_async.zig 2026-04-01 20:40:08.904999609 +0200
++++ answers/085_async.zig 2026-04-01 20:40:05.641933231 +0200
+@@ -37,7 +37,7 @@
+ const std = @import("std");
+
+ pub fn main(init: std.process.Init) !void {
+- const io = init.???;
++ const io = init.io;
+
+ // Get the current wall-clock time using the Io interface.
+ // Hint: Timestamp.now() takes an Io and a Clock type (.real = wall clock).
diff --git a/patches/patches/086_async2.patch b/patches/patches/086_async2.patch
new file mode 100644
index 0000000..7506a69
--- /dev/null
+++ b/patches/patches/086_async2.patch
@@ -0,0 +1,14 @@
+--- exercises/086_async2.zig 2026-04-01 19:22:50.017227542 +0200
++++ answers/086_async2.zig 2026-04-01 19:21:57.569158481 +0200
+@@ -38,9 +38,9 @@
+
+ // Now collect the result. What method on Future gives us
+ // the value, blocking if it isn't ready yet?
+- const answer = future.???(io);
++ const answer = future.await(io);
+
+- std.debug.print("The answer is: {}\n", .{answer});
++ std.debug.print("the answer is: {}\n", .{answer});
+ }
+
+ fn computeAnswer(a: u32, b: u32) u32 {
diff --git a/patches/patches/087_async3.patch b/patches/patches/087_async3.patch
new file mode 100644
index 0000000..8365e7a
--- /dev/null
+++ b/patches/patches/087_async3.patch
@@ -0,0 +1,18 @@
+--- exercises/087_async3.zig 2026-04-01 22:51:05.540094851 +0200
++++ answers/087_async3.zig 2026-04-01 22:50:44.579669189 +0200
+@@ -29,12 +29,12 @@
+ const io = init.io;
+
+ // Launch both tasks asynchronously.
+- var future_a = io.async(slowAdd, .{ 10, 20 });
+- var future_b = ???(slowMul, .{ 6, 7 });
++ var future_a = io.async(slowAdd, .{ 1, 2 });
++ var future_b = io.async(slowMul, .{ 6, 7 });
+
+ // Await both results.
+ const sum = future_a.await(io);
+- const product = future_b.???(io);
++ const product = future_b.await(io);
+
+ print("{} + {} = {}\n", .{ 1, 2, sum });
+ print("{} * {} = {}\n", .{ 6, 7, product });
diff --git a/patches/patches/088_async4.patch b/patches/patches/088_async4.patch
new file mode 100644
index 0000000..1faf30e
--- /dev/null
+++ b/patches/patches/088_async4.patch
@@ -0,0 +1,11 @@
+--- exercises/088_async4.zig 2026-04-01 23:17:31.066443941 +0200
++++ answers/088_async4.zig 2026-04-01 23:17:39.251612131 +0200
+@@ -38,7 +38,7 @@
+
+ // Wait for all tasks to finish.
+ // What Group method blocks until all tasks complete?
+- try group.???
++ try group.await(io);
+
+ print("All tasks finished!\n", .{});
+ }
diff --git a/patches/patches/089_async5.patch b/patches/patches/089_async5.patch
new file mode 100644
index 0000000..d2baa96
--- /dev/null
+++ b/patches/patches/089_async5.patch
@@ -0,0 +1,11 @@
+--- exercises/089_async5.zig 2026-04-01 23:40:40.505855238 +0200
++++ answers/089_async5.zig 2026-04-01 23:40:10.176236971 +0200
+@@ -40,7 +40,7 @@
+
+ // We don't want to wait 10 seconds!
+ // Which Future method requests cancellation AND returns the result?
+- const result = ???;
++ const result = future.cancel(io);
+
+ print("Task returned: {}\n", .{result});
+ }
diff --git a/patches/patches/090_async6.patch b/patches/patches/090_async6.patch
new file mode 100644
index 0000000..5ac777b
--- /dev/null
+++ b/patches/patches/090_async6.patch
@@ -0,0 +1,11 @@
+--- exercises/090_async6.zig 2026-04-02 10:25:34.016616118 +0200
++++ answers/090_async6.zig 2026-04-02 10:27:48.827144051 +0200
+@@ -47,7 +47,7 @@
+
+ // Wait for the first finisher.
+ // What Select method returns the first completed result?
+- const winner = ???;
++ const winner = try sel.await();
+
+ switch (winner) {
+ .hare => |msg| print("Hare: {s}\n", .{msg}),
diff --git a/patches/patches/091_async7.patch b/patches/patches/091_async7.patch
new file mode 100644
index 0000000..b4bab9b
--- /dev/null
+++ b/patches/patches/091_async7.patch
@@ -0,0 +1,13 @@
+--- exercises/091_async7.zig 2026-04-02 10:50:08.142508099 +0200
++++ answers/091_async7.zig 2026-04-02 10:49:59.629341593 +0200
+@@ -49,8 +49,8 @@
+ 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.mutex.lock(io) catch return;
++ defer state.mutex.unlock(io);
+
+ state.counter += 1;
+ }
diff --git a/patches/patches/092_async8.patch b/patches/patches/092_async8.patch
new file mode 100644
index 0000000..0ec9116
--- /dev/null
+++ b/patches/patches/092_async8.patch
@@ -0,0 +1,11 @@
+--- exercises/092_async8.zig 2026-04-02 10:49:27.925721496 +0200
++++ answers/092_async8.zig 2026-04-02 10:49:31.694795212 +0200
+@@ -43,7 +43,7 @@
+ // 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;
++ queue.putOne(io, @intCast(i)) catch return;
+ }
+ // Signal that we're done sending.
+ queue.close(io);
diff --git a/patches/patches/093_async9.patch b/patches/patches/093_async9.patch
new file mode 100644
index 0000000..f759921
--- /dev/null
+++ b/patches/patches/093_async9.patch
@@ -0,0 +1,11 @@
+--- exercises/093_async9.zig 2026-04-03 13:44:50.526780809 +0200
++++ answers/093_async9.zig 2026-04-03 13:44:54.957870294 +0200
+@@ -36,7 +36,7 @@
+ // Launch with a guaranteed separate thread.
+ // Which Io method guarantees true concurrency?
+ // (Hint: unlike io.async, this one can fail!)
+- var future = try io.???(compute, .{io});
++ var future = try io.concurrent(compute, .{io});
+
+ print("Main thread continues...\n", .{});
+
diff --git a/patches/patches/094_async10.patch b/patches/patches/094_async10.patch
new file mode 100644
index 0000000..ae0d26d
--- /dev/null
+++ b/patches/patches/094_async10.patch
@@ -0,0 +1,13 @@
+--- exercises/094_async10.zig 2026-04-03 14:25:16.600025924 +0200
++++ answers/094_async10.zig 2026-04-03 14:24:56.192615893 +0200
+@@ -50,8 +50,8 @@
+
+ // Protect this section from cancellation.
+ // What method swaps the cancel protection state?
+- const old = io.???(. blocked);
+- defer _ = io.???(old);
++ const old = io.swapCancelProtection(.blocked);
++ defer _ = io.swapCancelProtection(old);
+
+ // This sleep will NOT return error.Canceled even though
+ // we get canceled during it — protection is active!
diff --git a/patches/patches/095_quiz_async.patch b/patches/patches/095_quiz_async.patch
new file mode 100644
index 0000000..dbaae07
--- /dev/null
+++ b/patches/patches/095_quiz_async.patch
@@ -0,0 +1,52 @@
+--- exercises/095_quiz_async.zig 2026-04-03 18:04:53.577391455 +0200
++++ answers/095_quiz_async.zig 2026-04-03 18:05:42.570392172 +0200
+@@ -51,7 +51,7 @@
+ // Bug 1: The collector needs to lock before modifying
+ // shared state. What Mutex method acquires the lock?
+ self.mutex.lock(io) catch return;
+- self.mutex.???(io) catch return;
++ defer self.mutex.unlock(io);
+
+ switch (reading.sensor_type) {
+ .thermometer => self.temperature = reading.value,
+@@ -79,9 +79,9 @@
+ // Bug 2: io.async doesn't guarantee a separate thread.
+ // Which Io method guarantees true concurrency?
+ // (Don't forget: it can fail, so you need 'try'!)
+- try sensors.???(io, sensor, .{ io, &queue, .thermometer, 20 });
+- try sensors.???(io, sensor, .{ io, &queue, .hygrometer, 60 });
+- try sensors.???(io, sensor, .{ io, &queue, .anemometer, 10 });
++ try sensors.concurrent(io, sensor, .{ io, &queue, .thermometer, 20 });
++ try sensors.concurrent(io, sensor, .{ io, &queue, .hygrometer, 60 });
++ try sensors.concurrent(io, sensor, .{ io, &queue, .anemometer, 10 });
+
+ // Collector group: processes readings from the queue.
+ var collectors: std.Io.Group = .init;
+@@ -90,7 +90,6 @@
+ // Bug 3: Wait for ALL sensors to finish sending their readings.
+ // What Group method blocks until all tasks complete?
+ try sensors.await(io);
+- // try sensors.???(io);
+
+ // All sensors done — close the queue so the collector knows
+ // there's no more data coming.
+@@ -104,8 +103,8 @@
+ //
+ // Bug 4: Protect this section from cancellation.
+ // What Io method swaps the cancel protection state?
+- const old_protection = io.???(.blocked);
+- defer _ = io.???(old_protection);
++ const old_protection = io.swapCancelProtection(.blocked);
++ defer _ = io.swapCancelProtection(old_protection);
+
+ printGardenReport(&weather);
+ }
+@@ -127,7 +126,7 @@
+
+ // Bug 5: Send the reading into the queue.
+ // What Queue method sends a single element?
+- queue.???(io, reading) catch return;
++ queue.putOne(io, reading) catch return;
+ }
+ }
+
--
cgit v1.2.3
From 7fae6e0607854831422115674f9cc02893ee217d Mon Sep 17 00:00:00 2001
From: Chris Boesch
Date: Fri, 3 Apr 2026 19:07:10 +0200
Subject: improved timestamp comparison
---
build.zig | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
(limited to 'build.zig')
diff --git a/build.zig b/build.zig
index beb0d72..7bde273 100644
--- a/build.zig
+++ b/build.zig
@@ -436,17 +436,29 @@ const ZiglingStep = struct {
else
result.stderr;
+ // NOTE: exercise.output can never contain a CR character.
+ // See https://ziglang.org/documentation/master/#Source-Encoding.
+ const output = trimLines(b.allocator, raw_output) catch @panic("OOM");
+
// Validate the output.
var exercise_output = self.exercise.output;
- // Insert timestamp for exercise 84
+ // Insert timestamp for exercise 85
if (self.exercise.timestamp) {
+
+ // Compare timestamp from exercise with now, diff < 5 seconds is valid
var ts_buf: [20]u8 = undefined;
- const timestamp = std.fmt.bufPrint(&ts_buf, "{}", .{std.Io.Timestamp.now(io, .real).toSeconds()}) catch unreachable;
+ const ts_slice = output[14..24];
+ const ts_value = try std.fmt.parseInt(i64, ts_slice, 10);
+ const ts_build = std.Io.Timestamp.now(io, .real).toSeconds();
+ const ts_diff = @abs(ts_build - ts_value);
+ const timestamp = std.fmt.bufPrint(&ts_buf, "{}", .{if (ts_diff < 5) ts_value else ts_build}) catch unreachable;
+ // Insert timestamp into check string
var buf: [100]u8 = undefined;
const prefix_len = 14;
const placeholder_len = 11;
+
@memcpy(buf[0..prefix_len], exercise_output[0..prefix_len]);
@memcpy(buf[prefix_len..][0..timestamp.len], timestamp);
const suffix = exercise_output[prefix_len + placeholder_len ..];
@@ -457,9 +469,6 @@ const ZiglingStep = struct {
exercise_output = buf[0..total_len];
}
- // NOTE: exercise.output can never contain a CR character.
- // See https://ziglang.org/documentation/master/#Source-Encoding.
- const output = trimLines(b.allocator, raw_output) catch @panic("OOM");
if (!std.mem.eql(u8, output, exercise_output)) {
const red = red_bold_text;
const reset = reset_text;
--
cgit v1.2.3
From f6a6798c8b6b813bd2ceee81db276e05327a76e0 Mon Sep 17 00:00:00 2001
From: Chris Boesch
Date: Fri, 3 Apr 2026 19:28:12 +0200
Subject: improved report design
---
build.zig | 2 +-
exercises/084_interfaces.zig | 2 +-
patches/patches/084_interfaces.patch | 4 ++--
3 files changed, 4 insertions(+), 4 deletions(-)
(limited to 'build.zig')
diff --git a/build.zig b/build.zig
index 7bde273..d3d91ee 100644
--- a/build.zig
+++ b/build.zig
@@ -1121,7 +1121,7 @@ const exercises = [_]Exercise{
.{
.main_file = "084_interfaces.zig",
.output =
- \\Daily Insect Report:
+ \\=== Doctor Zoraptera's Insect Report ===
\\Ant is alive.
\\Bee visited 17 flowers.
\\Grasshopper hopped 32 meters.
diff --git a/exercises/084_interfaces.zig b/exercises/084_interfaces.zig
index 7775dd5..fd61ead 100644
--- a/exercises/084_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/patches/patches/084_interfaces.patch b/patches/patches/084_interfaces.patch
index a1d0628..d3a45fd 100644
--- a/patches/patches/084_interfaces.patch
+++ b/patches/patches/084_interfaces.patch
@@ -1,5 +1,5 @@
---- exercises/084_interfaces.zig 2025-08-15 15:17:57.839348063 +0200
-+++ answers/084_interfaces.zig 2026-04-03 14:27:32.670756488 +0200
+--- exercises/084_interfaces.zig 2026-04-03 19:24:51.764327692 +0200
++++ answers/084_interfaces.zig 2026-04-03 19:27:31.552579474 +0200
@@ -106,7 +106,7 @@
for (my_insects) |insect| {
// Almost done! We want to print() each insect with a
--
cgit v1.2.3
From 0206db8129e6dba87100958aa8d31e7c58628d2e Mon Sep 17 00:00:00 2001
From: Chris Boesch
Date: Fri, 3 Apr 2026 21:27:55 +0200
Subject: fixed missing uppercase letter
---
build.zig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'build.zig')
diff --git a/build.zig b/build.zig
index d3d91ee..3cddb61 100644
--- a/build.zig
+++ b/build.zig
@@ -1137,7 +1137,7 @@ const exercises = [_]Exercise{
},
.{
.main_file = "086_async2.zig",
- .output = "Computing... the answer is: 42",
+ .output = "Computing... The answer is: 42",
},
.{
.main_file = "087_async3.zig",
--
cgit v1.2.3
From 09bae6a70e51fc1313297bc98c67c252e8c32d3e Mon Sep 17 00:00:00 2001
From: Chris Boesch
Date: Mon, 6 Apr 2026 19:30:56 +0200
Subject: improvements for async-io
---
build.zig | 6 +++---
exercises/093_async9.zig | 42 ++++++++++++++++++++++++++--------------
patches/patches/093_async9.patch | 14 +++++++-------
3 files changed, 37 insertions(+), 25 deletions(-)
(limited to 'build.zig')
diff --git a/build.zig b/build.zig
index 3cddb61..dcafe14 100644
--- a/build.zig
+++ b/build.zig
@@ -1180,9 +1180,9 @@ const exercises = [_]Exercise{
.{
.main_file = "093_async9.zig",
.output =
- \\Main thread continues...
- \\Computing on a separate thread!
- \\Main thread done waiting.
+ \\Computing concurrently!
+ \\Main continues...
+ \\Main done waiting.
\\Result: 123
, // pay attention to the comma
},
diff --git a/exercises/093_async9.zig b/exercises/093_async9.zig
index ad30dcf..4a41544 100644
--- a/exercises/093_async9.zig
+++ b/exercises/093_async9.zig
@@ -5,22 +5,29 @@
// The difference:
//
// io.async():
-// * The function MAY run on another thread, or it may run
-// immediately on the current thread (synchronously).
-// * Never fails — if no thread is available, it just runs
-// the function right away.
+// * 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 (a real thread
-// in the Threaded backend).
+// * 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 true parallelism.
+// * 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!
@@ -33,25 +40,30 @@ const print = std.debug.print;
pub fn main(init: std.process.Init) !void {
const io = init.io;
- // Launch with a guaranteed separate thread.
- // Which Io method guarantees true concurrency?
+ // 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);
- print("Main thread continues...\n", .{});
-
- // Wait 100 millisecond so the output order is deterministic.
+ // 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 thread done waiting.\n", .{});
+ 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 on a separate thread!\n", .{});
+ print("Computing concurrently!\n", .{});
// Simulate some work.
- io.sleep(std.Io.Duration.fromMilliseconds(200), .awake) catch return 0;
+ io.sleep(std.Io.Duration.fromMilliseconds(400), .awake) catch return 0;
return 123;
}
diff --git a/patches/patches/093_async9.patch b/patches/patches/093_async9.patch
index f759921..ebdfce2 100644
--- a/patches/patches/093_async9.patch
+++ b/patches/patches/093_async9.patch
@@ -1,11 +1,11 @@
---- exercises/093_async9.zig 2026-04-03 13:44:50.526780809 +0200
-+++ answers/093_async9.zig 2026-04-03 13:44:54.957870294 +0200
-@@ -36,7 +36,7 @@
- // Launch with a guaranteed separate thread.
- // Which Io method guarantees true concurrency?
+--- exercises/093_async9.zig 2026-04-06 19:26:11.388025362 +0200
++++ answers/093_async9.zig 2026-04-06 19:18:36.242931688 +0200
+@@ -43,7 +43,7 @@
+ // 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});
+ var future = try io.concurrent(compute, .{io});
+ defer _ = future.cancel(io);
- print("Main thread continues...\n", .{});
-
+ // Note: All breaks in this excercise (using sleep)
--
cgit v1.2.3