mirror of
https://codeberg.org/ziglings/exercises.git
synced 2026-06-08 07:50:00 +00:00
Merge pull request 'revival of the async-io functions' (#383) from asyncIo into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/383
This commit is contained in:
@@ -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
|
||||
|
||||
236
build.zig
236
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);
|
||||
@@ -434,12 +436,40 @@ const ZiglingStep = struct {
|
||||
else
|
||||
result.stderr;
|
||||
|
||||
// Validate the output.
|
||||
// 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)) {
|
||||
|
||||
// Validate the output.
|
||||
var exercise_output = self.exercise.output;
|
||||
|
||||
// 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 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 ..];
|
||||
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];
|
||||
}
|
||||
|
||||
if (!std.mem.eql(u8, output, exercise_output)) {
|
||||
const red = red_bold_text;
|
||||
const reset = reset_text;
|
||||
|
||||
@@ -698,7 +728,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 +760,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 +897,7 @@ const exercises = [_]Exercise{
|
||||
\\ <span style="color: #00ff00">Green</span>
|
||||
\\ <span style="color: #0000ff">Blue</span>
|
||||
\\</p>
|
||||
,
|
||||
, // pay attention to the comma
|
||||
.hint = "I'm feeling blue about this.",
|
||||
},
|
||||
.{
|
||||
@@ -879,7 +909,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 +933,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 +977,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 +1073,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,103 +1111,128 @@ 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.",
|
||||
},
|
||||
.{
|
||||
.main_file = "083_anonymous_lists.zig",
|
||||
.output = "I say hello!",
|
||||
},
|
||||
.{
|
||||
.main_file = "084_interfaces.zig",
|
||||
.output =
|
||||
\\=== Doctor Zoraptera's 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",
|
||||
.output = "foo() A",
|
||||
.hint = "Read the facts. Use the facts.",
|
||||
.skip = true,
|
||||
.skip_hint = "async has not been implemented in the current compiler version.",
|
||||
.main_file = "085_async.zig",
|
||||
.output = "Current time: <timestamp>s since epoch",
|
||||
.timestamp = true,
|
||||
},
|
||||
.{
|
||||
.main_file = "085_async2.zig",
|
||||
.output = "Hello async!",
|
||||
.skip = true,
|
||||
.skip_hint = "async has not been implemented in the current compiler version.",
|
||||
.main_file = "086_async2.zig",
|
||||
.output = "Computing... the answer is: 42",
|
||||
},
|
||||
.{
|
||||
.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.",
|
||||
},
|
||||
.{
|
||||
.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.",
|
||||
},
|
||||
.{
|
||||
.main_file = "088_async5.zig",
|
||||
.output = "Example Title.",
|
||||
.skip = true,
|
||||
.skip_hint = "async has not been implemented in the current compiler version.",
|
||||
},
|
||||
.{
|
||||
.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.",
|
||||
},
|
||||
.{
|
||||
.main_file = "090_async7.zig",
|
||||
.output = "beef? BEEF!",
|
||||
.skip = true,
|
||||
.skip_hint = "async has not been implemented in the current compiler version.",
|
||||
},
|
||||
.{
|
||||
.main_file = "091_async8.zig",
|
||||
.output = "ABCDEF",
|
||||
.skip = true,
|
||||
.skip_hint = "async has not been implemented in the current compiler version.",
|
||||
},
|
||||
|
||||
.{
|
||||
.main_file = "092_interfaces.zig",
|
||||
.main_file = "087_async3.zig",
|
||||
.output =
|
||||
\\Daily Insect Report:
|
||||
\\Ant is alive.
|
||||
\\Bee visited 17 flowers.
|
||||
\\Grasshopper hopped 32 meters.
|
||||
,
|
||||
\\1 + 2 = 3
|
||||
\\6 * 7 = 42
|
||||
\\Total: 45
|
||||
, // pay attention to the comma
|
||||
},
|
||||
.{
|
||||
.main_file = "093_hello_c.zig",
|
||||
.main_file = "088_async4.zig",
|
||||
.output =
|
||||
\\Task 1 done.
|
||||
\\Task 2 done.
|
||||
\\Task 3 done.
|
||||
\\All tasks finished!
|
||||
, // pay attention to the comma
|
||||
},
|
||||
.{
|
||||
.main_file = "089_async5.zig",
|
||||
.output =
|
||||
\\Starting long computation...
|
||||
\\Canceling slow task...
|
||||
\\Task was canceled, cleaning up.
|
||||
\\Task returned: 0
|
||||
, // pay attention to the comma
|
||||
},
|
||||
.{
|
||||
.main_file = "090_async6.zig",
|
||||
.output = "Hare: I'm fast!",
|
||||
},
|
||||
.{
|
||||
.main_file = "091_async7.zig",
|
||||
.output = "Counter: 400",
|
||||
},
|
||||
.{
|
||||
.main_file = "092_async8.zig",
|
||||
.output = "Sum of 1..10 = 55",
|
||||
},
|
||||
.{
|
||||
.main_file = "093_async9.zig",
|
||||
.output =
|
||||
\\Main thread continues...
|
||||
\\Computing on a separate thread!
|
||||
\\Main thread done waiting.
|
||||
\\Result: 123
|
||||
, // pay attention to the comma
|
||||
},
|
||||
.{
|
||||
.main_file = "094_async10.zig",
|
||||
.output =
|
||||
\\Starting critical section...
|
||||
\\Critical section completed safely.
|
||||
\\Task result: All data saved.
|
||||
, // pay attention to the comma
|
||||
},
|
||||
.{
|
||||
.main_file = "095_quiz_async.zig",
|
||||
.output =
|
||||
\\=== 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
|
||||
},
|
||||
.{
|
||||
.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
|
||||
@@ -1211,25 +1269,25 @@ 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)
|
||||
\\3. Bard (Gold: 5, XP: 55)
|
||||
\\4. Warrior (Gold: 7392, XP: 21)
|
||||
,
|
||||
, // 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
|
||||
@@ -1247,10 +1305,10 @@ const exercises = [_]Exercise{
|
||||
\\and
|
||||
\\despair
|
||||
\\This little poem has 15 words!
|
||||
,
|
||||
, // pay attention to the comma
|
||||
},
|
||||
.{
|
||||
.main_file = "104_threading.zig",
|
||||
.main_file = "107_threading.zig",
|
||||
.output =
|
||||
\\Starting work...
|
||||
\\thread 1: started.
|
||||
@@ -1261,35 +1319,35 @@ const exercises = [_]Exercise{
|
||||
\\thread 1: finished.
|
||||
\\thread 3: finished.
|
||||
\\Zig is cool!
|
||||
,
|
||||
, // 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)
|
||||
@@ -1321,11 +1379,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 = "",
|
||||
},
|
||||
.{
|
||||
@@ -1334,6 +1392,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
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
//
|
||||
// Six Facts:
|
||||
//
|
||||
// 1. The memory space allocated to your program for the
|
||||
// invocation of a function and all of its data is called a
|
||||
// "stack frame".
|
||||
//
|
||||
// 2. The 'return' keyword "pops" the current function
|
||||
// invocation's frame off of the stack (it is no longer needed)
|
||||
// and returns control to the place where the function was
|
||||
// called.
|
||||
//
|
||||
// fn foo() void {
|
||||
// return; // Pop the frame and return control
|
||||
// }
|
||||
//
|
||||
// 3. Like 'return', the 'suspend' keyword returns control to the
|
||||
// place where the function was called BUT the function
|
||||
// invocation's frame remains so that it can regain control again
|
||||
// at a later time. Functions which do this are "async"
|
||||
// functions.
|
||||
//
|
||||
// fn fooThatSuspends() void {
|
||||
// suspend {} // return control, but leave the frame alone
|
||||
// }
|
||||
//
|
||||
// 4. To call any function in async context and get a reference
|
||||
// to its frame for later use, use the 'async' keyword:
|
||||
//
|
||||
// var foo_frame = async fooThatSuspends();
|
||||
//
|
||||
// 5. If you call an async function without the 'async' keyword,
|
||||
// the function FROM WHICH you called the async function itself
|
||||
// becomes async! In this example, the bar() function is now
|
||||
// async because it calls fooThatSuspends(), which is async.
|
||||
//
|
||||
// fn bar() void {
|
||||
// fooThatSuspends();
|
||||
// }
|
||||
//
|
||||
// 6. The main() function cannot be async!
|
||||
//
|
||||
// Given facts 3 and 4, how do we fix this program (broken by facts
|
||||
// 5 and 6)?
|
||||
//
|
||||
const print = @import("std").debug.print;
|
||||
|
||||
pub fn main() void {
|
||||
// Additional Hint: you can assign things to '_' when you
|
||||
// don't intend to do anything with them.
|
||||
foo();
|
||||
}
|
||||
|
||||
fn foo() void {
|
||||
print("foo() A\n", .{});
|
||||
suspend {}
|
||||
print("foo() B\n", .{});
|
||||
}
|
||||
@@ -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.
|
||||
48
exercises/085_async.zig
Normal file
48
exercises/085_async.zig
Normal file
@@ -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()});
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
//
|
||||
// So, 'suspend' returns control to the place from which it was
|
||||
// called (the "call site"). How do we give control back to the
|
||||
// suspended function?
|
||||
//
|
||||
// For that, we have a new keyword called 'resume' which takes an
|
||||
// async function invocation's frame and returns control to it.
|
||||
//
|
||||
// fn fooThatSuspends() void {
|
||||
// suspend {}
|
||||
// }
|
||||
//
|
||||
// var foo_frame = async fooThatSuspends();
|
||||
// resume foo_frame;
|
||||
//
|
||||
// See if you can make this program print "Hello async!".
|
||||
//
|
||||
const print = @import("std").debug.print;
|
||||
|
||||
pub fn main() void {
|
||||
var foo_frame = async foo();
|
||||
}
|
||||
|
||||
fn foo() void {
|
||||
print("Hello ", .{});
|
||||
suspend {}
|
||||
print("async!\n", .{});
|
||||
}
|
||||
48
exercises/086_async2.zig
Normal file
48
exercises/086_async2.zig
Normal file
@@ -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;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
//
|
||||
// Because they can suspend and resume, async Zig functions are
|
||||
// an example of a more general programming concept called
|
||||
// "coroutines". One of the neat things about Zig async functions
|
||||
// is that they retain their state as they are suspended and
|
||||
// resumed.
|
||||
//
|
||||
// See if you can make this program print "5 4 3 2 1".
|
||||
//
|
||||
const print = @import("std").debug.print;
|
||||
|
||||
pub fn main() void {
|
||||
const n = 5;
|
||||
var foo_frame = async foo(n);
|
||||
|
||||
???
|
||||
|
||||
print("\n", .{});
|
||||
}
|
||||
|
||||
fn foo(countdown: u32) void {
|
||||
var current = countdown;
|
||||
|
||||
while (current > 0) {
|
||||
print("{} ", .{current});
|
||||
current -= 1;
|
||||
suspend {}
|
||||
}
|
||||
}
|
||||
50
exercises/087_async3.zig
Normal file
50
exercises/087_async3.zig
Normal file
@@ -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;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
//
|
||||
// It has probably not escaped your attention that we are no
|
||||
// longer capturing a return value from foo() because the 'async'
|
||||
// keyword returns the frame instead.
|
||||
//
|
||||
// One way to solve this is to use a global variable.
|
||||
//
|
||||
// See if you can make this program print "1 2 3 4 5".
|
||||
//
|
||||
const print = @import("std").debug.print;
|
||||
|
||||
var global_counter: i32 = 0;
|
||||
|
||||
pub fn main() void {
|
||||
var foo_frame = async foo();
|
||||
|
||||
while (global_counter <= 5) {
|
||||
print("{} ", .{global_counter});
|
||||
???
|
||||
}
|
||||
|
||||
print("\n", .{});
|
||||
}
|
||||
|
||||
fn foo() void {
|
||||
while (true) {
|
||||
???
|
||||
???
|
||||
}
|
||||
}
|
||||
50
exercises/088_async4.zig
Normal file
50
exercises/088_async4.zig
Normal file
@@ -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});
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
//
|
||||
// Sure, we can solve our async value problem with a global
|
||||
// variable. But this hardly seems like an ideal solution.
|
||||
//
|
||||
// So how do we REALLY get return values from async functions?
|
||||
//
|
||||
// The 'await' keyword waits for an async function to complete
|
||||
// and then captures its return value.
|
||||
//
|
||||
// fn foo() u32 {
|
||||
// return 5;
|
||||
// }
|
||||
//
|
||||
// var foo_frame = async foo(); // invoke and get frame
|
||||
// var value = await foo_frame; // await result using frame
|
||||
//
|
||||
// The above example is just a silly way to call foo() and get 5
|
||||
// back. But if foo() did something more interesting such as wait
|
||||
// for a network response to get that 5, our code would pause
|
||||
// until the value was ready.
|
||||
//
|
||||
// As you can see, async/await basically splits a function call
|
||||
// into two parts:
|
||||
//
|
||||
// 1. Invoke the function ('async')
|
||||
// 2. Getting the return value ('await')
|
||||
//
|
||||
// Also notice that a 'suspend' keyword does NOT need to exist in
|
||||
// a function to be called in an async context.
|
||||
//
|
||||
// Please use 'await' to get the string returned by
|
||||
// getPageTitle().
|
||||
//
|
||||
const print = @import("std").debug.print;
|
||||
|
||||
pub fn main() void {
|
||||
var myframe = async getPageTitle("http://example.com");
|
||||
|
||||
var value = ???
|
||||
|
||||
print("{s}\n", .{value});
|
||||
}
|
||||
|
||||
fn getPageTitle(url: []const u8) []const u8 {
|
||||
// Please PRETEND this is actually making a network request.
|
||||
_ = url;
|
||||
return "Example Title.";
|
||||
}
|
||||
61
exercises/089_async5.zig
Normal file
61
exercises/089_async5.zig
Normal file
@@ -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;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
//
|
||||
// The power and purpose of async/await becomes more apparent
|
||||
// when we do multiple things concurrently. Foo and Bar do not
|
||||
// depend on each other and can happen at the same time, but End
|
||||
// requires that they both be finished.
|
||||
//
|
||||
// +---------+
|
||||
// | Start |
|
||||
// +---------+
|
||||
// / \
|
||||
// / \
|
||||
// +---------+ +---------+
|
||||
// | Foo | | Bar |
|
||||
// +---------+ +---------+
|
||||
// \ /
|
||||
// \ /
|
||||
// +---------+
|
||||
// | End |
|
||||
// +---------+
|
||||
//
|
||||
// We can express this in Zig like so:
|
||||
//
|
||||
// fn foo() u32 { ... }
|
||||
// fn bar() u32 { ... }
|
||||
//
|
||||
// // Start
|
||||
//
|
||||
// var foo_frame = async foo();
|
||||
// var bar_frame = async bar();
|
||||
//
|
||||
// var foo_value = await foo_frame;
|
||||
// var bar_value = await bar_frame;
|
||||
//
|
||||
// // End
|
||||
//
|
||||
// Please await TWO page titles!
|
||||
//
|
||||
const print = @import("std").debug.print;
|
||||
|
||||
pub fn main() void {
|
||||
var com_frame = async getPageTitle("http://example.com");
|
||||
var org_frame = async getPageTitle("http://example.org");
|
||||
|
||||
var com_title = com_frame;
|
||||
var org_title = org_frame;
|
||||
|
||||
print(".com: {s}, .org: {s}.\n", .{ com_title, org_title });
|
||||
}
|
||||
|
||||
fn getPageTitle(url: []const u8) []const u8 {
|
||||
// Please PRETEND this is actually making a network request.
|
||||
_ = url;
|
||||
return "Example Title";
|
||||
}
|
||||
71
exercises/090_async6.zig
Normal file
71
exercises/090_async6.zig
Normal file
@@ -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...";
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
//
|
||||
// Remember how a function with 'suspend' is async and calling an
|
||||
// async function without the 'async' keyword makes the CALLING
|
||||
// function async?
|
||||
//
|
||||
// fn fooThatMightSuspend(maybe: bool) void {
|
||||
// if (maybe) suspend {}
|
||||
// }
|
||||
//
|
||||
// fn bar() void {
|
||||
// fooThatMightSuspend(true); // Now bar() is async!
|
||||
// }
|
||||
//
|
||||
// But if you KNOW the function won't suspend, you can make a
|
||||
// promise to the compiler with the 'nosuspend' keyword:
|
||||
//
|
||||
// fn bar() void {
|
||||
// nosuspend fooThatMightSuspend(false);
|
||||
// }
|
||||
//
|
||||
// If the function does suspend and YOUR PROMISE TO THE COMPILER
|
||||
// IS BROKEN, the program will panic at runtime, which is
|
||||
// probably better than you deserve, you oathbreaker! >:-(
|
||||
//
|
||||
const print = @import("std").debug.print;
|
||||
|
||||
pub fn main() void {
|
||||
|
||||
// The main() function can not be async. But we know
|
||||
// getBeef() will not suspend with this particular
|
||||
// invocation. Please make this okay:
|
||||
var my_beef = getBeef(0);
|
||||
|
||||
print("beef? {X}!\n", .{my_beef});
|
||||
}
|
||||
|
||||
fn getBeef(input: u32) u32 {
|
||||
if (input == 0xDEAD) {
|
||||
suspend {}
|
||||
}
|
||||
|
||||
return 0xBEEF;
|
||||
}
|
||||
//
|
||||
// Going Deeper Into...
|
||||
// ...uNdeFiNEd beHAVi0r!
|
||||
//
|
||||
// We haven't discussed it yet, but runtime "safety" features
|
||||
// require some extra instructions in your compiled program.
|
||||
// Most of the time, you're going to want to keep these in.
|
||||
//
|
||||
// But in some programs, when data integrity is less important
|
||||
// than raw speed (some games, for example), you can compile
|
||||
// without these safety features.
|
||||
//
|
||||
// Instead of a safe panic when something goes wrong, your
|
||||
// program will now exhibit Undefined Behavior (UB), which simply
|
||||
// means that the Zig language does not (cannot) define what will
|
||||
// happen. The best case is that it will crash, but in the worst
|
||||
// case, it will continue to run with the wrong results and
|
||||
// corrupt your data or expose you to security risks.
|
||||
//
|
||||
// This program is a great way to explore UB. Once you get it
|
||||
// working, try calling the getBeef() function with the value
|
||||
// 0xDEAD so that it will invoke the 'suspend' keyword:
|
||||
//
|
||||
// getBeef(0xDEAD)
|
||||
//
|
||||
// Now when you run the program, it will panic and give you a
|
||||
// nice stack trace to help debug the problem.
|
||||
//
|
||||
// zig run exercises/090_async7.zig
|
||||
// thread 328 panic: async function called...
|
||||
// ...
|
||||
//
|
||||
// But see what happens when you turn off safety checks by using
|
||||
// ReleaseFast mode:
|
||||
//
|
||||
// zig run -O ReleaseFast exercises/090_async7.zig
|
||||
// beef? 0!
|
||||
//
|
||||
// This is the wrong result. On your computer, you may get a
|
||||
// different answer or it might crash! What exactly will happen
|
||||
// is UNDEFINED. Your computer is now like a wild animal,
|
||||
// reacting to bits and bytes of raw memory with the base
|
||||
// instincts of the CPU. It is both terrifying and exhilarating.
|
||||
//
|
||||
57
exercises/091_async7.zig
Normal file
57
exercises/091_async7.zig
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
//
|
||||
// You have doubtless noticed that 'suspend' requires a block
|
||||
// expression like so:
|
||||
//
|
||||
// suspend {}
|
||||
//
|
||||
// The suspend block executes when a function suspends. To get
|
||||
// sense for when this happens, please make the following
|
||||
// program print the string
|
||||
//
|
||||
// "ABCDEF"
|
||||
//
|
||||
const print = @import("std").debug.print;
|
||||
|
||||
pub fn main() void {
|
||||
print("A", .{});
|
||||
|
||||
var frame = async suspendable();
|
||||
|
||||
print("X", .{});
|
||||
|
||||
resume frame;
|
||||
|
||||
print("F", .{});
|
||||
}
|
||||
|
||||
fn suspendable() void {
|
||||
print("X", .{});
|
||||
|
||||
suspend {
|
||||
print("X", .{});
|
||||
}
|
||||
|
||||
print("X", .{});
|
||||
}
|
||||
62
exercises/092_async8.zig
Normal file
62
exercises/092_async8.zig
Normal file
@@ -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});
|
||||
}
|
||||
57
exercises/093_async9.zig
Normal file
57
exercises/093_async9.zig
Normal file
@@ -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;
|
||||
}
|
||||
67
exercises/094_async10.zig
Normal file
67
exercises/094_async10.zig
Normal file
@@ -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.";
|
||||
}
|
||||
186
exercises/095_quiz_async.zig
Normal file
186
exercises/095_quiz_async.zig
Normal file
@@ -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.
|
||||
@@ -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,
|
||||
11
patches/patches/084_async.patch
Normal file
11
patches/patches/084_async.patch
Normal file
@@ -0,0 +1,11 @@
|
||||
--- exercises/084_async.zig 2026-04-01 20:40:08.904999609 +0200
|
||||
+++ answers/084_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).
|
||||
@@ -1,5 +1,5 @@
|
||||
--- exercises/092_interfaces.zig 2023-10-03 22:15:22.125574535 +0200
|
||||
+++ answers/092_interfaces.zig 2023-10-05 20:04:07.259437354 +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
|
||||
11
patches/patches/085_async.patch
Normal file
11
patches/patches/085_async.patch
Normal file
@@ -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).
|
||||
14
patches/patches/085_async2.patch
Normal file
14
patches/patches/085_async2.patch
Normal file
@@ -0,0 +1,14 @@
|
||||
--- exercises/085_async2.zig 2026-04-01 19:22:50.017227542 +0200
|
||||
+++ answers/085_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 {
|
||||
14
patches/patches/086_async2.patch
Normal file
14
patches/patches/086_async2.patch
Normal file
@@ -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 {
|
||||
18
patches/patches/086_async3.patch
Normal file
18
patches/patches/086_async3.patch
Normal file
@@ -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 });
|
||||
18
patches/patches/087_async3.patch
Normal file
18
patches/patches/087_async3.patch
Normal file
@@ -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 });
|
||||
11
patches/patches/087_async4.patch
Normal file
11
patches/patches/087_async4.patch
Normal file
@@ -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", .{});
|
||||
}
|
||||
11
patches/patches/088_async4.patch
Normal file
11
patches/patches/088_async4.patch
Normal file
@@ -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", .{});
|
||||
}
|
||||
11
patches/patches/088_async5.patch
Normal file
11
patches/patches/088_async5.patch
Normal file
@@ -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});
|
||||
}
|
||||
11
patches/patches/089_async5.patch
Normal file
11
patches/patches/089_async5.patch
Normal file
@@ -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});
|
||||
}
|
||||
11
patches/patches/089_async6.patch
Normal file
11
patches/patches/089_async6.patch
Normal file
@@ -0,0 +1,11 @@
|
||||
--- exercises/089_async6.zig 2026-04-02 10:25:34.016616118 +0200
|
||||
+++ answers/089_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}),
|
||||
11
patches/patches/090_async6.patch
Normal file
11
patches/patches/090_async6.patch
Normal file
@@ -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}),
|
||||
13
patches/patches/090_async7.patch
Normal file
13
patches/patches/090_async7.patch
Normal file
@@ -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;
|
||||
}
|
||||
13
patches/patches/091_async7.patch
Normal file
13
patches/patches/091_async7.patch
Normal file
@@ -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;
|
||||
}
|
||||
11
patches/patches/091_async8.patch
Normal file
11
patches/patches/091_async8.patch
Normal file
@@ -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);
|
||||
11
patches/patches/092_async8.patch
Normal file
11
patches/patches/092_async8.patch
Normal file
@@ -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);
|
||||
11
patches/patches/092_async9.patch
Normal file
11
patches/patches/092_async9.patch
Normal file
@@ -0,0 +1,11 @@
|
||||
--- exercises/092_async9.zig 2026-04-03 13:44:50.526780809 +0200
|
||||
+++ answers/092_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", .{});
|
||||
|
||||
13
patches/patches/093_async10.patch
Normal file
13
patches/patches/093_async10.patch
Normal file
@@ -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!
|
||||
11
patches/patches/093_async9.patch
Normal file
11
patches/patches/093_async9.patch
Normal file
@@ -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", .{});
|
||||
|
||||
13
patches/patches/094_async10.patch
Normal file
13
patches/patches/094_async10.patch
Normal file
@@ -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!
|
||||
11
patches/patches/095_interfaces.patch
Normal file
11
patches/patches/095_interfaces.patch
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
52
patches/patches/095_quiz_async.patch
Normal file
52
patches/patches/095_quiz_async.patch
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
--- 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
|
||||
--- 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,
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
--- 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
|
||||
--- 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({
|
||||
@@ -1,5 +1,5 @@
|
||||
--- exercises/095_for3.zig 2026-02-27 19:33:59
|
||||
+++ answers/095_for3.zig 2026-02-27 19:33:38
|
||||
--- 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
|
||||
@@ -1,5 +1,5 @@
|
||||
--- 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
|
||||
--- 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();
|
||||
|
||||
11
patches/patches/100_bit_manipulation.patch
Normal file
11
patches/patches/100_bit_manipulation.patch
Normal file
@@ -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 });
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
--- 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
|
||||
--- 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
|
||||
//
|
||||
@@ -1,5 +1,5 @@
|
||||
--- exercises/099_formatting.zig 2024-11-07 21:45:10.459123650 +0100
|
||||
+++ answers/099_formatting.zig 2024-11-07 21:43:55.154345991 +0100
|
||||
--- 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
|
||||
@@ -1,5 +1,5 @@
|
||||
--- exercises/100_for4.zig 2023-10-03 22:15:22.125574535 +0200
|
||||
+++ answers/100_for4.zig 2023-10-05 20:04:07.296104707 +0200
|
||||
--- 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 };
|
||||
@@ -1,5 +1,5 @@
|
||||
--- exercises/101_for5.zig 2023-10-03 22:15:22.125574535 +0200
|
||||
+++ answers/101_for5.zig 2023-10-05 20:04:07.299438103 +0200
|
||||
--- 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.
|
||||
@@ -1,5 +1,5 @@
|
||||
--- exercises/102_testing.zig 2025-10-24 12:54:56
|
||||
+++ answers/102_testing.zig 2025-10-24 12:56:33
|
||||
--- 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.
|
||||
@@ -1,5 +1,5 @@
|
||||
--- exercises/103_tokenization.zig 2026-02-27 19:25:11
|
||||
+++ answers/103_tokenization.zig 2026-02-27 19:26:04
|
||||
--- 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 @@
|
||||
;
|
||||
|
||||
@@ -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/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
|
||||
@@ -1,5 +1,5 @@
|
||||
--- exercises/105_threading2.zig 2024-03-23 16:35:14.754540802 +0100
|
||||
+++ answers/105_threading2.zig 2024-03-23 16:38:00.577539733 +0100
|
||||
--- 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();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
--- exercises/106_files.zig 2026-01-09 22:41:19.373872684 +0100
|
||||
+++ answers/106_files.zig 2026-01-09 22:41:44.518372910 +0100
|
||||
--- 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
|
||||
//
|
||||
@@ -1,5 +1,5 @@
|
||||
--- exercises/107_files2.zig 2026-01-09 22:43:15.211177186 +0100
|
||||
+++ answers/107_files2.zig 2026-01-09 22:42:48.943654602 +0100
|
||||
--- 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
|
||||
@@ -1,6 +1,6 @@
|
||||
--- 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 @@
|
||||
--- 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,
|
||||
@@ -1,5 +1,5 @@
|
||||
--- exercises/109_vectors.zig 2024-11-07 14:57:09.673383618 +0100
|
||||
+++ answers/109_vectors.zig 2024-11-07 14:22:59.069150138 +0100
|
||||
--- 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);
|
||||
@@ -1,6 +1,6 @@
|
||||
--- 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 @@
|
||||
--- 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});
|
||||
@@ -9,7 +9,7 @@
|
||||
checkAnswer(0b1001, PORTB);
|
||||
|
||||
newline();
|
||||
@@ -116,7 +116,7 @@
|
||||
@@ -92,7 +92,7 @@
|
||||
PORTB = 0b1100;
|
||||
print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
|
||||
print("^ {b:0>4} // (bitmask)\n", .{0b0011});
|
||||
@@ -18,7 +18,7 @@
|
||||
checkAnswer(0b1111, PORTB);
|
||||
|
||||
newline();
|
||||
@@ -170,7 +170,7 @@
|
||||
@@ -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});
|
||||
@@ -27,7 +27,7 @@
|
||||
checkAnswer(0b1101, PORTB);
|
||||
|
||||
newline();
|
||||
@@ -178,7 +178,7 @@
|
||||
@@ -111,7 +111,7 @@
|
||||
PORTB = 0b1001; // reset PORTB
|
||||
print(" {b:0>4} // (reset state)\n", .{PORTB});
|
||||
print("| {b:0>4} // (bitmask)\n", .{0b0100});
|
||||
@@ -36,7 +36,7 @@
|
||||
checkAnswer(0b1101, PORTB);
|
||||
|
||||
newline();
|
||||
@@ -269,7 +269,7 @@
|
||||
@@ -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});
|
||||
@@ -45,7 +45,7 @@
|
||||
checkAnswer(0b1010, PORTB);
|
||||
|
||||
newline();
|
||||
@@ -277,7 +277,7 @@
|
||||
@@ -130,7 +130,7 @@
|
||||
PORTB = 0b0111; // reset PORTB
|
||||
print(" {b:0>4} // (reset state)\n", .{PORTB});
|
||||
print("& {b:0>4} // (bitmask)\n", .{0b1110});
|
||||
@@ -1,5 +1,5 @@
|
||||
--- exercises/111_packed.zig 2026-03-13 11:18:44
|
||||
+++ answers/111_packed.zig 2026-03-13 11:18: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 {
|
||||
@@ -1,5 +1,5 @@
|
||||
--- exercises/112_packed2.zig 2026-03-13 11:14:08
|
||||
+++ answers/112_packed2.zig 2026-03-13 11:14:16
|
||||
--- 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) {
|
||||
Reference in New Issue
Block a user