CR2DN5WK5YRJEWSZ5EKL2ROY3YGPXBHGO2DGNW6AJFBF6RAYOUQAC const std = @import("std");const print = std.debug.print;const string = []const u8;const cwd = std.fs.cwd();const Dir = std.fs.Dir;const Allocator = std.mem.Allocator;const EXERCISES_PATH = "exercises";const HEALED_PATH = "patches/healed";const TEMP_PATH = "patches/healed/tmp";const PATCHES_PATH = "patches/patches";// Heals all the exercises.fn heal(alloc: Allocator) !void {try cwd.makePath(HEALED_PATH);const org_path = try cwd.realpathAlloc(alloc, EXERCISES_PATH);const patch_path = try cwd.realpathAlloc(alloc, PATCHES_PATH);const healed_path = try cwd.realpathAlloc(alloc, HEALED_PATH);var idir = try cwd.openIterableDir(EXERCISES_PATH, Dir.OpenDirOptions{});defer idir.close();var it = idir.iterate();while (try it.next()) |entry| {// create filenamesconst healed_file = try concat(alloc, &.{ healed_path, "/", entry.name });const patch_file = try concat(alloc, &.{ patch_path, "/", try patch_name(alloc, entry.name) });// patch fileconst result = try std.ChildProcess.exec(.{.allocator = alloc,.argv = &.{ "patch", "-i", patch_file, "-o", healed_file, entry.name },.cwd = org_path,});print("{s}", .{result.stderr});}}// Yields all the healed exercises that are not correctly formatted.fn check_healed(alloc: Allocator) !void {try cwd.makePath(TEMP_PATH);const temp_path = try cwd.realpathAlloc(alloc, TEMP_PATH);const healed_path = try cwd.realpathAlloc(alloc, HEALED_PATH);var idir = try cwd.openIterableDir(HEALED_PATH, Dir.OpenDirOptions{});defer idir.close();var it = idir.iterate();while (try it.next()) |entry| {// Check the healed fileconst result = try std.ChildProcess.exec(.{.allocator = alloc,.argv = &.{ "zig", "fmt", "--check", entry.name },.cwd = healed_path,});// Is there something to fix?if (result.stdout.len > 0) {const temp_file = try concat(alloc, &.{ temp_path, "/", entry.name });const healed_file = try concat(alloc, &.{ healed_path, "/", entry.name });try std.fs.copyFileAbsolute(healed_file, temp_file, std.fs.CopyFileOptions{});// Formats the temp file_ = try std.ChildProcess.exec(.{.allocator = alloc,.argv = &.{ "zig", "fmt", entry.name },.cwd = temp_path,});// Show the differencesconst diff = try std.ChildProcess.exec(.{.allocator = alloc,.argv = &.{ "diff", "-c", healed_file, entry.name },.cwd = temp_path,});print("{s}", .{diff.stdout});try std.fs.deleteFileAbsolute(temp_file);}}}fn concat(alloc: Allocator, slices: []const string) !string {const buf = try std.mem.concat(alloc, u8, slices);return buf;}fn patch_name(alloc: Allocator, path: string) !string {var filename = path;const index = std.mem.lastIndexOfScalar(u8, path, '.') orelse return path;if (index > 0) filename = path[0..index];return try concat(alloc, &.{ filename, ".patch" });}pub fn main() !void {var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);defer arena.deinit();const alloc = arena.allocator();try heal(alloc);try check_healed(alloc);}
const std = @import("std");const root = @import("../build.zig");const debug = std.debug;const fmt = std.fmt;const fs = std.fs;const mem = std.mem;const Allocator = std.mem.Allocator;const Child = std.process.Child;const Build = std.Build;const LazyPath = std.Build.LazyPath;const Reader = fs.File.Reader;const RunStep = std.Build.RunStep;const Step = Build.Step;const Exercise = root.Exercise;pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {const step = b.step("test-cli", "Test the command line interface");{// Test that `zig build -Dhealed -Dn=n` selects the nth exercise.const case_step = createCase(b, "case-1");const tmp_path = makeTempPath(b) catch |err| {return fail(step, "unable to make tmp path: {s}\n", .{@errorName(err)});};const heal_step = HealStep.create(b, exercises, tmp_path);for (exercises[0 .. exercises.len - 1]) |ex| {const n = ex.number();const cmd = b.addSystemCommand(&.{b.graph.zig_exe,"build","-Dhealed",b.fmt("-Dhealed-path={s}", .{tmp_path}),b.fmt("-Dn={}", .{n}),});cmd.setName(b.fmt("zig build -Dhealed -Dn={}", .{n}));cmd.expectExitCode(0);cmd.step.dependOn(&heal_step.step);const stderr = cmd.captureStdErr();const verify = CheckNamedStep.create(b, ex, stderr);verify.step.dependOn(&cmd.step);case_step.dependOn(&verify.step);}const cleanup = b.addRemoveDirTree(.{ .src_path = .{ .owner = b, .sub_path = tmp_path } });cleanup.step.dependOn(case_step);step.dependOn(&cleanup.step);}{// Test that `zig build -Dhealed` processes all the exercises in order.const case_step = createCase(b, "case-2");const tmp_path = makeTempPath(b) catch |err| {return fail(step, "unable to make tmp path: {s}\n", .{@errorName(err)});};const heal_step = HealStep.create(b, exercises, tmp_path);heal_step.step.dependOn(case_step);// TODO: when an exercise is modified, the cache is not invalidated.const cmd = b.addSystemCommand(&.{b.graph.zig_exe,"build","-Dhealed",b.fmt("-Dhealed-path={s}", .{tmp_path}),});cmd.setName("zig build -Dhealed");cmd.expectExitCode(0);cmd.step.dependOn(&heal_step.step);const stderr = cmd.captureStdErr();const verify = CheckStep.create(b, exercises, stderr);verify.step.dependOn(&cmd.step);const cleanup = b.addRemoveDirTree(.{ .src_path = .{ .owner = b, .sub_path = tmp_path } });cleanup.step.dependOn(&verify.step);step.dependOn(&cleanup.step);}{// Test that `zig build -Dn=n` prints the hint.const case_step = createCase(b, "case-3");for (exercises[0 .. exercises.len - 1]) |ex| {if (ex.skip) continue;if (ex.hint) |hint| {const n = ex.number();const cmd = b.addSystemCommand(&.{b.graph.zig_exe,"build",b.fmt("-Dn={}", .{n}),});cmd.setName(b.fmt("zig build -Dn={}", .{n}));cmd.expectExitCode(2);cmd.addCheck(.{ .expect_stderr_match = hint });case_step.dependOn(&cmd.step);}}step.dependOn(case_step);}return step;}fn createCase(b: *Build, name: []const u8) *Step {const case_step = b.allocator.create(Step) catch @panic("OOM");case_step.* = Step.init(.{.id = .custom,.name = name,.owner = b,});return case_step;}/// Checks the output of `zig build -Dn=n`.const CheckNamedStep = struct {step: Step,exercise: Exercise,stderr: LazyPath,pub fn create(owner: *Build, exercise: Exercise, stderr: LazyPath) *CheckNamedStep {const self = owner.allocator.create(CheckNamedStep) catch @panic("OOM");self.* = .{.step = Step.init(.{.id = .custom,.name = "check-named",.owner = owner,.makeFn = make,}),.exercise = exercise,.stderr = stderr,};return self;}fn make(step: *Step, _: Step.MakeOptions) !void {const b = step.owner;const self: *CheckNamedStep = @alignCast(@fieldParentPtr("step", step));const ex = self.exercise;const stderr_file = try fs.cwd().openFile(self.stderr.getPath(b),.{ .mode = .read_only },);defer stderr_file.close();var stderr = stderr_file.readerStreaming(&.{});{// Skip the logo.const nlines = mem.count(u8, root.logo, "\n");var buf: [80]u8 = undefined;var lineno: usize = 0;while (lineno < nlines) : (lineno += 1) {_ = try readLine(&stderr, &buf);}}try check_output(step, ex, &stderr);}};/// Checks the output of `zig build`.const CheckStep = struct {step: Step,exercises: []const Exercise,stderr: LazyPath,pub fn create(owner: *Build,exercises: []const Exercise,stderr: LazyPath,) *CheckStep {const self = owner.allocator.create(CheckStep) catch @panic("OOM");self.* = .{.step = Step.init(.{.id = .custom,.name = "check",.owner = owner,.makeFn = make,}),.exercises = exercises,.stderr = stderr,};return self;}fn make(step: *Step, _: Step.MakeOptions) !void {const b = step.owner;const self: *CheckStep = @alignCast(@fieldParentPtr("step", step));const exercises = self.exercises;const stderr_file = try fs.cwd().openFile(self.stderr.getPath(b),.{ .mode = .read_only },);defer stderr_file.close();var stderr = stderr_file.readerStreaming(&.{});for (exercises) |ex| {if (ex.number() == 1) {// Skip the logo.const nlines = mem.count(u8, root.logo, "\n");var buf: [80]u8 = undefined;var lineno: usize = 0;while (lineno < nlines) : (lineno += 1) {_ = try readLine(&stderr, &buf);}}try check_output(step, ex, &stderr);}}};fn check_output(step: *Step, exercise: Exercise, reader: *Reader) !void {const b = step.owner;var buf: [1024]u8 = undefined;if (exercise.skip) {{const actual = try readLine(reader, &buf) orelse "EOF";const expect = b.fmt("Skipping {s}", .{exercise.main_file});try check(step, exercise, expect, actual);}{const actual = try readLine(reader, &buf) orelse "EOF";try check(step, exercise, "", actual);}return;}{const actual = try readLine(reader, &buf) orelse "EOF";const expect = b.fmt("Compiling {s}...", .{exercise.main_file});try check(step, exercise, expect, actual);}{const actual = try readLine(reader, &buf) orelse "EOF";const expect = b.fmt("Checking {s}...", .{exercise.main_file});try check(step, exercise, expect, actual);}{const actual = try readLine(reader, &buf) orelse "EOF";const expect = switch (exercise.kind) {.exe => "PASSED:",.@"test" => "PASSED",};try check(step, exercise, expect, actual);}// Skip the exercise output.const nlines = switch (exercise.kind) {.exe => 1 + mem.count(u8, exercise.output, "\n") + 1,.@"test" => 1,};var lineno: usize = 0;while (lineno < nlines) : (lineno += 1) {_ = try readLine(reader, &buf) orelse @panic("EOF");}}fn check(step: *Step,exercise: Exercise,expect: []const u8,actual: []const u8,) !void {if (!mem.eql(u8, expect, actual)) {return step.fail("{s}: expected to see \"{s}\", found \"{s}\"", .{exercise.main_file,expect,actual,});}}fn readLine(reader: *fs.File.Reader, buf: []u8) !?[]const u8 {try reader.interface.readSliceAll(buf);return mem.trimRight(u8, buf, " \r\n");}/// Fails with a custom error message.const FailStep = struct {step: Step,error_msg: []const u8,pub fn create(owner: *Build, error_msg: []const u8) *FailStep {const self = owner.allocator.create(FailStep) catch @panic("OOM");self.* = .{.step = Step.init(.{.id = .custom,.name = "fail",.owner = owner,.makeFn = make,}),.error_msg = error_msg,};return self;}fn make(step: *Step, _: Step.MakeOptions) !void {const b = step.owner;const self: *FailStep = @alignCast(@fieldParentPtr("step", step));try step.result_error_msgs.append(b.allocator, self.error_msg);return error.MakeFailed;}};/// A variant of `std.Build.Step.fail` that does not return an error so that it/// can be used in the configuration phase. It returns a FailStep, so that the/// error will be cleanly handled by the build runner.fn fail(step: *Step, comptime format: []const u8, args: anytype) *Step {const b = step.owner;const fail_step = FailStep.create(b, b.fmt(format, args));step.dependOn(&fail_step.step);return step;}/// Heals the exercises.const HealStep = struct {step: Step,exercises: []const Exercise,work_path: []const u8,pub fn create(owner: *Build, exercises: []const Exercise, work_path: []const u8) *HealStep {const self = owner.allocator.create(HealStep) catch @panic("OOM");self.* = .{.step = Step.init(.{.id = .custom,.name = "heal",.owner = owner,.makeFn = make,}),.exercises = exercises,.work_path = work_path,};return self;}fn make(step: *Step, _: Step.MakeOptions) !void {const b = step.owner;const self: *HealStep = @alignCast(@fieldParentPtr("step", step));return heal(b.allocator, self.exercises, self.work_path);}};/// Heals all the exercises.fn heal(allocator: Allocator, exercises: []const Exercise, work_path: []const u8) !void {const sep = std.fs.path.sep_str;const join = fs.path.join;const exercises_path = "exercises";const patches_path = "patches" ++ sep ++ "patches";for (exercises) |ex| {const name = ex.name();const file = try join(allocator, &.{ exercises_path, ex.main_file });const patch = b: {const patch_name = try fmt.allocPrint(allocator, "{s}.patch", .{name});break :b try join(allocator, &.{ patches_path, patch_name });};const output = try join(allocator, &.{ work_path, ex.main_file });const argv = &.{ "patch", "-i", patch, "-o", output, "-s", file };var child = Child.init(argv, allocator);_ = try child.spawnAndWait();}}/// This function is the same as the one in std.Build.makeTempPath, with the/// difference that returns an error when the temp path cannot be created.pub fn makeTempPath(b: *Build) ![]const u8 {const rand_int = std.crypto.random.int(u64);const rand_hex64 = std.fmt.hex(rand_int);const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ rand_hex64;const path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch@panic("OOM");try b.cache_root.handle.makePath(tmp_dir_sub_path);return path;}
--- exercises/999_the_end.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/999_the_end.zig 2023-10-05 20:04:07.312771687 +0200@@ -6,3 +6,4 @@pub fn main() void {print("\nThis is the end for now!\nWe hope you had fun and were able to learn a lot, so visit us again when the next exercises are available.\n", .{});}+// gollum's line ;-)
--- 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 PORTBprint(" {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 PORTBprint(" {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 PORTBprint(" {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 PORTBprint(" {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();
--- 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;}
--- 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", .{});}
--- exercises/107_files2.zig 2025-03-13 15:26:59.532367792 +0200+++ answers/107_files2.zig 2025-03-14 22:08:35.167953736 +0200@@ -33,7 +33,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});@@ -41,12 +41,12 @@// can you go here to find a way to read the content?// https://ziglang.org/documentation/master/std/#std.fs.File// hint: you might find two answers that are both valid in this case- const bytes_read = zig_read_the_file_or_i_will_fight_you(&content);+ const bytes_read = try file.read(&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});}
--- exercises/106_files.zig 2025-03-13 15:26:59.532367792 +0200+++ answers/106_files.zig 2025-03-14 22:04:52.243435159 +0200@@ -35,7 +35,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 throughelse => return e,};@@ -44,7 +44,7 @@// wait a minute...// opening a directory might fail!// what should we do here?- var output_dir: std.fs.Dir = cwd.openDir("output", .{});+ var output_dir: std.fs.Dir = try cwd.openDir("output", .{});defer output_dir.close();// we try to open the file `zigling.txt`,@@ -55,7 +55,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();+ defer file.close();// you are not allowed to move these two lines above the file closing line!const byte_written = try file.write("It's zigling time!");
--- 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});
--- exercises/104_threading.zig 2024-04-10 19:12:29.878856370 +0200+++ answers/104_threading.zig 2024-04-10 19:11:22.304265713 +0200@@ -97,12 +97,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 threadconst 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.
--- exercises/103_tokenization.zig 2023-10-05 21:57:23.245974688 +0200+++ answers/103_tokenization.zig 2023-10-05 22:06:08.319119156 +0200@@ -136,7 +136,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 themvar cnt: usize = 0;
--- exercises/102_testing.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/102_testing.zig 2023-10-05 20:04:07.302771500 +0200@@ -83,7 +83,7 @@// 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);}@@ -108,5 +108,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));}
--- 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",
--- 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;
--- 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:
--- 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;}
--- 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 });}
--- 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: ", .{});
--- exercises/095_for3.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/095_for3.zig 2023-10-05 20:04:07.272770937 +0200@@ -54,7 +54,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.
--- 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 {
--- 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});
--- 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();}}
--- exercises/083_anonymous_lists.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/083_anonymous_lists.zig 2023-10-05 20:04:07.216103210 +0200@@ -20,6 +20,6 @@//// = .{ 'h', 'e', 'l', 'l', 'o' };//- const hello = .{ 'h', 'e', 'l', 'l', 'o' };+ const hello: [5]u8 = .{ 'h', 'e', 'l', 'l', 'o' };print("I say {s}!\n", .{hello});}
--- exercises/082_anonymous_structs3.zig 2025-03-14 16:41:17.892873287 +0200+++ answers/082_anonymous_structs3.zig 2025-03-14 16:40:56.043829543 +0200@@ -82,14 +82,14 @@// @typeInfo(Circle).@"struct".fields//// This will be an array of StructFields.- const fields = ???;+ const fields = @typeInfo(@TypeOf(tuple)).@"struct".fields;// 2. Loop through each field. This must be done at compile// time.//// Hint: remember 'inline' loops?//- for (fields) |field| {+ inline for (fields) |field| {// 3. Print the field's name, type, and value.//// Each 'field' in this loop is one of these:@@ -119,9 +119,9 @@//// The first field should print as: "0"(bool):trueprint("\"{s}\"({any}):{any} ", .{- field.???,- field.???,- ???,+ field.name,+ field.type,+ @field(tuple, field.name),});}}
--- exercises/081_anonymous_structs2.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/081_anonymous_structs2.zig 2023-10-05 20:04:07.209436419 +0200@@ -38,7 +38,7 @@// Please complete this function which prints an anonymous struct// representing a circle.-fn printCircle(???) void {+fn printCircle(circle: anytype) void {print("x:{} y:{} radius:{}\n", .{circle.center_x,circle.center_y,
--- exercises/080_anonymous_structs.zig 2023-11-21 14:52:54.312749682 +0100+++ answers/080_anonymous_structs.zig 2023-11-21 14:52:43.909225238 +0100@@ -48,13 +48,13 @@// * circle1 should hold i32 integers// * circle2 should hold f32 floats//- const circle1 = ??? {+ const circle1 = Circle(i32){.center_x = 25,.center_y = 70,.radius = 15,};- const circle2 = ??? {+ const circle2 = Circle(f32){.center_x = 25.234,.center_y = 70.999,.radius = 15.714,
--- exercises/079_quoted_identifiers.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/079_quoted_identifiers.zig 2023-10-05 20:04:07.199436232 +0200@@ -20,11 +20,11 @@const print = @import("std").debug.print;pub fn main() void {- const 55_cows: i32 = 55;- const isn't true: bool = false;+ const @"55_cows": i32 = 55;+ const @"isn't true": bool = false;print("Sweet freedom: {}, {}.\n", .{- 55_cows,- isn't true,+ @"55_cows",+ @"isn't true",});}
--- exercises/078_sentinels3.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/078_sentinels3.zig 2023-10-05 20:04:07.196102836 +0200@@ -21,7 +21,7 @@const data: [*]const u8 = "Weird Data!";// Please cast 'data' to 'printable':- const printable: [*:0]const u8 = ???;+ const printable: [*:0]const u8 = @ptrCast(data);print("{s}\n", .{printable});}
--- exercises/077_sentinels2.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/077_sentinels2.zig 2023-10-05 20:04:07.189436043 +0200@@ -60,7 +60,7 @@// length... You've actually solved this problem before!//// Here's a big hint: do you remember how to take a slice?- const printable = ???;+ const printable = foo.data[0..foo.length];print("{s}\n", .{printable});}
--- exercises/076_sentinels.zig 2024-09-02 19:27:04.336781039 +0200+++ answers/076_sentinels.zig 2024-09-02 19:26:15.709134934 +0200@@ -82,7 +82,7 @@print("Array:", .{});// Loop through the items in my_seq.- for (???) |s| {+ for (my_seq) |s| {print("{}", .{s});}},@@ -94,7 +94,7 @@// Loop through the items in my_seq until we hit the// sentinel value.var i: usize = 0;- while (??? != my_sentinel) {+ while (my_seq[i] != my_sentinel) {print("{}", .{my_seq[i]});i += 1;}
--- exercises/075_quiz8.zig 2023-11-21 14:48:15.440702720 +0100+++ answers/075_quiz8.zig 2023-11-21 14:50:23.453311616 +0100@@ -49,7 +49,11 @@//// Please fill in the body of this function!fn makePath(from: *Place, to: *Place, dist: u8) Path {-+ return Path{+ .from = from,+ .to = to,+ .dist = dist,+ };}// Using our new function, these path definitions take up considerably less
--- exercises/074_comptime9.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/074_comptime9.zig 2023-10-05 20:04:07.176102462 +0200@@ -39,7 +39,7 @@// And here's the function. Note that the return value type// depends on one of the input arguments!-fn makeLlamas(count: usize) [count]u8 {+fn makeLlamas(comptime count: usize) [count]u8 {var temp: [count]u8 = undefined;var i = 0;
--- exercises/073_comptime8.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/073_comptime8.zig 2023-10-05 20:04:07.172769065 +0200@@ -32,12 +32,12 @@pub fn main() void {// We meant to fetch the last llama. Please fix this simple// mistake so the assertion no longer fails.- const my_llama = getLlama(5);+ const my_llama = getLlama(4);print("My llama value is {}.\n", .{my_llama});}-fn getLlama(i: usize) u32 {+fn getLlama(comptime i: usize) u32 {// We've put a guard assert() at the top of this function to// prevent mistakes. The 'comptime' keyword here means that// the mistake will be caught when we compile!
--- exercises/072_comptime7.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/072_comptime7.zig 2023-10-05 20:04:07.169435669 +0200@@ -35,7 +35,7 @@// at compile time.//// Please fix this to loop once per "instruction":- ??? (i < instructions.len) : (???) {+ inline while (i < instructions.len) : (i += 3) {// This gets the digit from the "instruction". Can you// figure out why we subtract '0' from it?
--- exercises/071_comptime6.zig 2024-09-02 19:21:50.250454978 +0200+++ answers/071_comptime6.zig 2024-09-02 19:21:23.553250563 +0200@@ -40,7 +40,7 @@const fields = @typeInfo(Narcissus).@"struct".fields;- ??? {+ inline for (fields) |field| {if (field.type != void) {print(" {s}", .{field.name});}
--- exercises/070_comptime5.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/070_comptime5.zig 2023-10-05 20:04:07.159435482 +0200@@ -123,8 +123,8 @@// Please make sure MyType has both waddle() and quack()// methods:const MyType = @TypeOf(possible_duck);- const walks_like_duck = ???;- const quacks_like_duck = ???;+ const walks_like_duck = @hasDecl(MyType, "waddle");+ const quacks_like_duck = @hasDecl(MyType, "quack");const is_duck = walks_like_duck and quacks_like_duck;
--- exercises/069_comptime4.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/069_comptime4.zig 2023-10-05 20:04:07.152768692 +0200@@ -42,8 +42,8 @@// 2) Sets the size of the array of type T (which is the// sequence we're creating and returning).//-fn makeSequence(comptime T: type, ??? size: usize) [???]T {- var sequence: [???]T = undefined;+fn makeSequence(comptime T: type, comptime size: usize) [size]T {+ var sequence: [size]T = undefined;var i: usize = 0;while (i < size) : (i += 1) {
--- exercises/068_comptime3.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/068_comptime3.zig 2023-10-05 20:04:07.149435295 +0200@@ -43,7 +43,7 @@//// Please change this so that it sets a 0 scale to 1// instead.- if (my_scale == 0) @compileError("Scale 1:0 is not valid!");+ if (my_scale == 0) my_scale = 1; //@compileError("Scale 1:0 is not valid!");self.scale = my_scale;self.hull_length /= my_scale;@@ -69,7 +69,7 @@// Hey, we can't just pass this runtime variable as an// argument to the scaleMe() method. What would let us do// that?- var scale: u32 = undefined;+ comptime var scale: u32 = undefined;scale = 32; // 1:32 scale
--- exercises/067_comptime2.zig 2023-11-21 14:36:12.080295365 +0100+++ answers/067_comptime2.zig 2023-11-21 15:11:50.814098876 +0100@@ -35,7 +35,7 @@// In this contrived example, we've decided to allocate some// arrays using a variable count! But something's missing...//- var count = 0;+ comptime var count = 0;count += 1;const a1: [count]u8 = .{'A'} ** count;
--- exercises/066_comptime.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/066_comptime.zig 2023-10-05 20:04:07.139435109 +0200@@ -62,8 +62,8 @@// types with specific sizes. The comptime numbers will be// coerced (if they'll fit!) into your chosen runtime types.// For this it is necessary to specify a size, e.g. 32 bit.- var var_int = 12345;- var var_float = 987.654;+ var var_int: u32 = 12345;+ var var_float: f32 = 987.654;// We can change what is stored at the areas set aside for// "var_int" and "var_float" in the running compiled program.
--- exercises/065_builtins2.zig 2025-06-17 13:58:07.857258167 +0200+++ answers/065_builtins2.zig 2025-06-17 13:56:36.630415938 +0200@@ -58,7 +58,7 @@// Oops! We cannot leave the 'me' and 'myself' fields// undefined. Please set them here:narcissus.me = &narcissus;- narcissus.??? = ???;+ narcissus.myself = &narcissus;// This determines a "peer type" from three separate// references (they just happen to all be the same object).@@ -70,7 +70,7 @@//// The fix for this is very subtle, but it makes a big// difference!- const Type2 = narcissus.fetchTheMostBeautifulType();+ const Type2 = Narcissus.fetchTheMostBeautifulType();// Now we print a pithy statement about Narcissus.print("A {s} loves all {s}es. ", .{@@ -113,15 +113,15 @@// Please complete these 'if' statements so that the field// name will not be printed if the field is of type 'void'// (which is a zero-bit type that takes up no space at all!):- if (fields[0].??? != void) {+ if (fields[0].type != void) {print(" {s}", .{fields[0].name});}- if (fields[1].??? != void) {+ if (fields[1].type != void) {print(" {s}", .{fields[1].name});}- if (fields[2].??? != void) {+ if (fields[2].type != void) {print(" {s}", .{fields[2].name});}
--- exercises/064_builtins.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/064_builtins.zig 2023-10-05 20:04:07.132768316 +0200@@ -63,7 +63,7 @@//// If there was no overflow at all while adding 5 to a, what value would// 'my_result' hold? Write the answer in into 'expected_result'.- const expected_result: u8 = ???;+ const expected_result: u8 = 0b00010010;print(". Without overflow: {b:0>8}. ", .{expected_result});print("Furthermore, ", .{});@@ -78,6 +78,6 @@// Now it's your turn. See if you can fix this attempt to use// this builtin to reverse the bits of a u8 integer.const input: u8 = 0b11110000;- const tupni: u8 = @bitReverse(input, tupni);+ const tupni: u8 = @bitReverse(input);print("{b:0>8} backwards is {b:0>8}.\n", .{ input, tupni });}
--- exercises/063_labels.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/063_labels.zig 2023-10-05 20:04:07.126101525 +0200@@ -128,8 +128,8 @@// wanted for this Food.//// Please return this Food from the loop.- break;- };+ break food;+ } else menu[0];// ^ Oops! We forgot to return Mac & Cheese as the default// Food when the requested ingredients aren't found.
--- exercises/062_loop_expressions.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/062_loop_expressions.zig 2023-10-05 20:04:07.122768129 +0200@@ -47,7 +47,7 @@// return it from the for loop.const current_lang: ?[]const u8 = for (langs) |lang| {if (lang.len == 3) break lang;- };+ } else null;if (current_lang) |cl| {print("Current language: {s}\n", .{cl});
--- exercises/061_coercions.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/061_coercions.zig 2023-10-05 20:04:07.119434735 +0200@@ -67,7 +67,7 @@pub fn main() void {var letter: u8 = 'A';- const my_letter: ??? = &letter;+ const my_letter: ?*[1]u8 = &letter;// ^^^^^^^// Your type here.// Must coerce from &letter (which is a *u8).
--- exercises/060_floats.zig 2025-03-03 20:23:40.255443963 +0400+++ answers/060_floats.zig 2025-03-03 20:29:58.554854977 +0400@@ -43,7 +43,7 @@//// We'll convert this weight from pounds to metric units at a// conversion of 0.453592 kg to the pound.- const shuttle_weight: f16 = 0.453592 * 4480e3;+ const shuttle_weight: f32 = 0.453592 * 4.480e3;// By default, float values are formatted in scientific// notation. Try experimenting with '{d}' and '{d:.3}' to see
--- exercises/059_integers.zig 2023-10-03 22:15:22.125574535 +0200+++ answers/059_integers.zig 2023-10-05 20:04:07.109434546 +0200@@ -20,9 +20,9 @@pub fn main() void {const zig = [_]u8{- 0o131, // octal- 0b1101000, // binary- 0x66, // hex+ 0o132, // octal+ 0b1101001, // binary+ 0x67, // hex};print("{s} is cool.\n", .{zig});
--- exercises/058_quiz7.zig 2024-10-28 09:06:49.448505460 +0100+++ answers/058_quiz7.zig 2024-10-28 09:35:14.631932322 +0100@@ -192,8 +192,8 @@// Oops! The hermit forgot how to capture the union values// in a switch statement. Please capture each value as// 'p' so the print statements work!- .place => print("{s}", .{p.name}),- .path => print("--{}->", .{p.dist}),+ .place => |p| print("{s}", .{p.name}),+ .path => |p| print("--{}->", .{p.dist}),}}};@@ -255,7 +255,7 @@// dereference and optional value "unwrapping" look// together. Remember that you return the address with the// "&" operator.- if (place == entry.*.?.place) return entry;+ if (place == entry.*.?.place) return &entry.*.?;// Try to make your answer this long:__________;}return null;@@ -309,7 +309,7 @@//// Looks like the hermit forgot something in the return value of// this function. What could that be?- fn getTripTo(self: *HermitsNotebook, trip: []?TripItem, dest: *Place) void {+ fn getTripTo(self: *HermitsNotebook, trip: []?TripItem, dest: *Place) TripError!void {// We start at the destination entry.const destination_entry = self.getEntry(dest);
--- exercises/057_unions3.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/057_unions3.zig 2023-10-05 20:04:07.099434359 +0200@@ -15,7 +15,7 @@//const std = @import("std");-const Insect = union(InsectStat) {+const Insect = union(enum) {flowers_visited: u16,still_alive: bool,};
--- exercises/056_unions2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/056_unions2.zig 2023-10-05 20:04:07.096100965 +0200@@ -44,14 +44,14 @@std.debug.print("Insect report! ", .{});// Could it really be as simple as just passing the union?- printInsect(???);- printInsect(???);+ printInsect(ant);+ printInsect(bee);std.debug.print("\n", .{});}fn printInsect(insect: Insect) void {- switch (???) {+ switch (insect) {.still_alive => |a| std.debug.print("Ant alive is: {}. ", .{a}),.flowers_visited => |f| std.debug.print("Bee visited {} flowers. ", .{f}),}
--- exercises/055_unions.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/055_unions.zig 2023-10-05 20:04:07.092767568 +0200@@ -59,8 +59,8 @@std.debug.print("Insect report! ", .{});// Oops! We've made a mistake here.- printInsect(ant, AntOrBee.c);- printInsect(bee, AntOrBee.c);+ printInsect(ant, AntOrBee.a);+ printInsect(bee, AntOrBee.b);std.debug.print("\n", .{});}
--- exercises/054_manypointers.zig 2023-10-22 13:59:15.818523309 +0200+++ answers/054_manypointers.zig 2023-10-22 14:05:24.095241143 +0200@@ -33,7 +33,7 @@// we can CONVERT IT TO A SLICE. (Hint: we do know the length!)//// Please fix this line so the print statement below can print it:- const zen12_string: []const u8 = zen_manyptr;+ const zen12_string: []const u8 = zen_manyptr[0..21];// Here's the moment of truth!std.debug.print("{s}\n", .{zen12_string});
--- exercises/053_slices2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/053_slices2.zig 2023-10-05 20:04:07.082767381 +0200@@ -17,19 +17,19 @@pub fn main() void {const scrambled = "great base for all your justice are belong to us";- const base1: []u8 = scrambled[15..23];- const base2: []u8 = scrambled[6..10];- const base3: []u8 = scrambled[32..];+ const base1: []const u8 = scrambled[15..23];+ const base2: []const u8 = scrambled[6..10];+ const base3: []const u8 = scrambled[32..];printPhrase(base1, base2, base3);- const justice1: []u8 = scrambled[11..14];- const justice2: []u8 = scrambled[0..5];- const justice3: []u8 = scrambled[24..31];+ const justice1: []const u8 = scrambled[11..14];+ const justice2: []const u8 = scrambled[0..5];+ const justice3: []const u8 = scrambled[24..31];printPhrase(justice1, justice2, justice3);std.debug.print("\n", .{});}-fn printPhrase(part1: []u8, part2: []u8, part3: []u8) void {+fn printPhrase(part1: []const u8, part2: []const u8, part3: []const u8) void {std.debug.print("'{s} {s} {s}.' ", .{ part1, part2, part3 });}
--- exercises/052_slices.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/052_slices.zig 2023-10-05 20:04:07.079433985 +0200@@ -32,8 +32,8 @@var cards = [8]u8{ 'A', '4', 'K', '8', '5', '2', 'Q', 'J' };// Please put the first 4 cards in hand1 and the rest in hand2.- const hand1: []u8 = cards[???];- const hand2: []u8 = cards[???];+ const hand1: []u8 = cards[0..4];+ const hand2: []u8 = cards[4..];std.debug.print("Hand1: ", .{});printHand(hand1);@@ -43,7 +43,7 @@}// Please lend this function a hand. A u8 slice hand, that is.-fn printHand(hand: ???) void {+fn printHand(hand: []u8) void {for (hand) |h| {std.debug.print("{u} ", .{h});}
--- exercises/051_values.zig 2024-03-14 23:25:42.695020607 +0100+++ answers/051_values.zig 2024-03-14 23:28:34.525109174 +0100@@ -87,7 +87,7 @@// Let's assign the std.debug.print function to a const named// "print" so that we can use this new name later!- const print = ???;+ const print = std.debug.print;// Now let's look at assigning and pointing to values in Zig.//@@ -163,13 +163,13 @@print("XP before:{}, ", .{glorp.experience});// Fix 1 of 2 goes here:- levelUp(glorp, reward_xp);+ levelUp(&glorp, reward_xp);print("after:{}.\n", .{glorp.experience});}// Fix 2 of 2 goes here:-fn levelUp(character_access: Character, xp: u32) void {+fn levelUp(character_access: *Character, xp: u32) void {character_access.experience += xp;}
--- exercises/050_no_value.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/050_no_value.zig 2023-10-05 20:04:07.069433797 +0200@@ -65,10 +65,10 @@const Err = error{Cthulhu};pub fn main() void {- var first_line1: *const [16]u8 = ???;+ var first_line1: *const [16]u8 = undefined;first_line1 = "That is not dead";- var first_line2: Err!*const [21]u8 = ???;+ var first_line2: Err!*const [21]u8 = Err.Cthulhu;first_line2 = "which can eternal lie";// Note we need the "{!s}" format for the error union string.@@ -77,8 +77,8 @@printSecondLine();}-fn printSecondLine() ??? {- var second_line2: ?*const [18]u8 = ???;+fn printSecondLine() void {+ var second_line2: ?*const [18]u8 = null;second_line2 = "even death may die";std.debug.print("And with strange aeons {s}.\n", .{second_line2.?});
--- exercises/049_quiz6.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/049_quiz6.zig 2023-10-05 20:04:07.062767005 +0200@@ -26,9 +26,13 @@// Your Elephant trunk methods go here!// ---------------------------------------------------+ pub fn getTrunk(self: *Elephant) *Elephant {+ return self.trunk.?;+ }- ???-+ pub fn hasTrunk(self: *Elephant) bool {+ return (self.trunk != null);+ }// ---------------------------------------------------pub fn visit(self: *Elephant) void {
--- exercises/048_methods2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/048_methods2.zig 2023-10-05 20:04:07.059433611 +0200@@ -54,7 +54,7 @@// This gets the next elephant or stops:// which method do we want here?- e = if (e.hasTail()) e.??? else break;+ e = if (e.hasTail()) e.getTail() else break;}}
--- exercises/047_methods.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/047_methods.zig 2023-10-05 20:04:07.056100214 +0200@@ -88,7 +88,7 @@for (&aliens) |*alien| {// *** Zap the alien with the heat ray here! ***- ???.zap(???);+ heat_ray.zap(alien);// If the alien's health is still above 0, it's still alive.if (alien.health > 0) aliens_alive += 1;
--- exercises/046_optionals2.zig 2024-11-08 22:46:25.592875338 +0100+++ answers/046_optionals2.zig 2024-11-08 22:46:20.699447951 +0100@@ -22,7 +22,7 @@const Elephant = struct {letter: u8,- tail: *Elephant = null, // Hmm... tail needs something...+ tail: ?*Elephant = null, // Hmm... tail needs something...visited: bool = false,};@@ -66,6 +66,6 @@// HINT: We want something similar to what `.?` does,// but instead of ending the program, we want to exit the loop...- e = e.tail ???+ e = e.tail orelse break;}}
--- exercises/045_optionals.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/045_optionals.zig 2023-10-05 20:04:07.046100027 +0200@@ -29,7 +29,7 @@// Please threaten the result so that answer is either the// integer value from deepThought() OR the number 42:- const answer: u8 = result;+ const answer: u8 = result orelse 42;std.debug.print("The Ultimate Answer: {}.\n", .{answer});}
--- exercises/044_quiz5.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/044_quiz5.zig 2023-10-05 20:04:07.039433235 +0200@@ -19,12 +19,14 @@pub fn main() void {var elephantA = Elephant{ .letter = 'A' };// (Please add Elephant B here!)+ var elephantB = Elephant{ .letter = 'B' };var elephantC = Elephant{ .letter = 'C' };// Link the elephants so that each tail "points" to the next elephant.// They make a circle: A->B->C->A...elephantA.tail = &elephantB;// (Please link Elephant B's tail to Elephant C here!)+ elephantB.tail = &elephantC;elephantC.tail = &elephantA;visitElephants(&elephantA);
--- exercises/043_pointers5.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/043_pointers5.zig 2023-10-05 20:04:07.036099841 +0200@@ -68,7 +68,7 @@// FIX ME!// Please pass Glorp to printCharacter():- printCharacter(???);+ printCharacter(&glorp);}// Note how this function's "c" parameter is a pointer to a Character struct.
--- exercises/042_pointers4.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/042_pointers4.zig 2023-10-05 20:04:07.032766444 +0200@@ -37,5 +37,5 @@// This function should take a reference to a u8 value and set it// to 5.fn makeFive(x: *u8) void {- ??? = 5; // fix me!+ x.* = 5; // fix me!}
--- exercises/041_pointers3.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/041_pointers3.zig 2023-10-05 20:04:07.026099654 +0200@@ -31,7 +31,7 @@// Please define pointer "p" so that it can point to EITHER foo or// bar AND change the value it points to!- ??? p: ??? = undefined;+ var p: *u8 = undefined;p = &foo;p.* += 1;
--- exercises/040_pointers2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/040_pointers2.zig 2023-10-05 20:04:07.022766257 +0200@@ -23,7 +23,7 @@pub fn main() void {const a: u8 = 12;- const b: *u8 = &a; // fix this!+ const b: *const u8 = &a; // fix this!std.debug.print("a: {}, b: {}\n", .{ a, b.* });}
--- exercises/039_pointers.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/039_pointers.zig 2023-10-05 20:04:07.016099467 +0200@@ -30,7 +30,7 @@// Please make num2 equal 5 using num1_pointer!// (See the "cheatsheet" above for ideas.)- num2 = ???;+ num2 = num1_pointer.*;std.debug.print("num1: {}, num2: {}\n", .{ num1, num2 });}
--- exercises/038_structs2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/038_structs2.zig 2023-10-05 20:04:07.012766070 +0200@@ -42,6 +42,12 @@//// Feel free to run this program without adding Zump. What does// it do and why?+ chars[1] = Character{+ .role = Role.bard,+ .gold = 10,+ .health = 100,+ .experience = 20,+ };// Printing all RPG characters in a loop:for (chars, 0..) |c, num| {
--- exercises/037_structs.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/037_structs.zig 2023-10-05 20:04:07.009432674 +0200@@ -36,6 +36,7 @@role: Role,gold: u32,experience: u32,+ health: u8,};pub fn main() void {@@ -44,6 +45,7 @@.role = Role.wizard,.gold = 20,.experience = 10,+ .health = 100,};// Glorp gains some gold.
--- exercises/036_enums2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/036_enums2.zig 2023-10-05 20:04:07.002765884 +0200@@ -31,7 +31,7 @@const Color = enum(u32) {red = 0xff0000,green = 0x00ff00,- blue = ???,+ blue = 0x0000ff,};pub fn main() void {@@ -53,12 +53,12 @@\\<p>\\ <span style="color: #{x:0>6}">Red</span>\\ <span style="color: #{x:0>6}">Green</span>- \\ <span style="color: #{}">Blue</span>+ \\ <span style="color: #{x:0>6}">Blue</span>\\</p>\\, .{@intFromEnum(Color.red),@intFromEnum(Color.green),- @intFromEnum(???), // Oops! We're missing something!+ @intFromEnum(Color.blue), // Oops! We're missing something!});}
--- exercises/035_enums.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/035_enums.zig 2023-10-05 20:04:06.999432487 +0200@@ -20,7 +20,7 @@const std = @import("std");// Please complete the enum!-const Ops = enum { ??? };+const Ops = enum { dec, inc, pow };pub fn main() void {const operations = [_]Ops{
--- exercises/034_quiz4.zig 2025-07-22 09:55:51.337832401 +0200+++ answers/034_quiz4.zig 2025-07-22 10:05:08.320323184 +0200@@ -9,10 +9,10 @@const NumError = error{IllegalNumber};-pub fn main() void {+pub fn main() !void {var stdout = std.fs.File.stdout().writer(&.{});- const my_num: u32 = getNumber();+ const my_num: u32 = try getNumber();try stdout.interface.print("my_num={}\n", .{my_num});}
--- exercises/033_iferror.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/033_iferror.zig 2023-10-05 20:04:06.989432300 +0200@@ -39,6 +39,7 @@std.debug.print("={}. ", .{value});} else |err| switch (err) {MyNumberError.TooBig => std.debug.print(">4. ", .{}),+ MyNumberError.TooSmall => std.debug.print("<4. ", .{}),// Please add a match for TooSmall here and have it print: "<4. "}}
--- exercises/032_unreachable.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/032_unreachable.zig 2023-10-05 20:04:06.986098904 +0200@@ -35,6 +35,7 @@3 => {current_value *= current_value;},+ else => unreachable,}std.debug.print("{} ", .{current_value});
--- exercises/031_switch2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/031_switch2.zig 2023-10-05 20:04:06.979432113 +0200@@ -31,6 +31,7 @@26 => 'Z',// As in the last exercise, please add the 'else' clause// and this time, have it return an exclamation mark '!'.+ else => '!',};std.debug.print("{c}", .{real_char});
--- exercises/030_switch.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/030_switch.zig 2023-10-05 20:04:06.976098717 +0200@@ -46,6 +46,7 @@// match for every possible value). Please add an "else"// to this switch to print a question mark "?" when c is// not one of the existing matches.+ else => std.debug.print("?", .{}),}}
--- exercises/029_errdefer.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/029_errdefer.zig 2023-10-05 20:04:06.972765320 +0200@@ -32,7 +32,7 @@// Please make the "failed" message print ONLY if the makeNumber()// function exits with an error:- std.debug.print("failed!\n", .{});+ errdefer std.debug.print("failed!\n", .{});var num = try getNumber(); // <-- This could fail!
--- exercises/028_defer2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/028_defer2.zig 2023-10-05 20:04:06.966098530 +0200@@ -18,7 +18,7 @@fn printAnimal(animal: u8) void {std.debug.print("(", .{});- std.debug.print(") ", .{}); // <---- how?!+ defer std.debug.print(") ", .{}); // <---- how?!if (animal == 'g') {std.debug.print("Goat", .{});
--- exercises/027_defer.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/027_defer.zig 2023-10-05 20:04:06.962765133 +0200@@ -20,6 +20,6 @@pub fn main() void {// Without changing anything else, please add a 'defer' statement// to this code so that our program prints "One Two\n":- std.debug.print("Two\n", .{});+ defer std.debug.print("Two\n", .{});std.debug.print("One ", .{});}
--- exercises/026_hello2.zig 2025-07-22 09:55:51.337832401 +0200+++ answers/026_hello2.zig 2025-07-22 10:00:11.233348058 +0200@@ -23,5 +23,5 @@// to be able to pass it up as a return value of main().//// We just learned of a single statement which can accomplish this.- stdout.interface.print("Hello world!\n", .{});+ try stdout.interface.print("Hello world!\n", .{});}
--- exercises/025_errors5.zig 2023-11-21 14:22:48.159250165 +0100+++ answers/025_errors5.zig 2023-11-21 14:25:01.338277886 +0100@@ -26,7 +26,7 @@// This function needs to return any error which might come back from detect().// Please use a "try" statement rather than a "catch".//- const x = detect(n);+ const x = try detect(n);return x + 5;}
--- exercises/024_errors4.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/024_errors4.zig 2023-10-05 20:04:06.949431550 +0200@@ -59,7 +59,13 @@// If we get a TooSmall error, we should return 10.// If we get any other error, we should return that error.// Otherwise, we return the u32 number.- return detectProblems(n) ???;+ return detectProblems(n) catch |err| {+ if (err == MyNumberError.TooSmall) {+ return 10;+ }++ return err;+ };}fn detectProblems(n: u32) MyNumberError!u32 {
--- exercises/023_errors3.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/023_errors3.zig 2023-10-05 20:04:06.946098156 +0200@@ -12,14 +12,14 @@pub fn main() void {const a: u32 = addTwenty(44) catch 22;- const b: u32 = addTwenty(4) ??? 22;+ const b: u32 = addTwenty(4) catch 22;std.debug.print("a={}, b={}\n", .{ a, b });}// Please provide the return type from this function.// Hint: it'll be an error union.-fn addTwenty(n: u32) ??? {+fn addTwenty(n: u32) MyNumberError!u32 {if (n < 5) {return MyNumberError.TooSmall;} else {
--- exercises/022_errors2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/022_errors2.zig 2023-10-05 20:04:06.939431363 +0200@@ -19,7 +19,7 @@const MyNumberError = error{TooSmall};pub fn main() void {- var my_number: ??? = 5;+ var my_number: MyNumberError!u8 = 5;// Looks like my_number will need to either store a number OR// an error. Can you set the type correctly above?
--- exercises/021_errors.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/021_errors.zig 2023-10-05 20:04:06.936097967 +0200@@ -9,7 +9,7 @@// "TooSmall". Please add it where needed!const MyNumberError = error{TooBig,- ???,+ TooSmall,TooFour,};@@ -26,7 +26,7 @@if (number_error == MyNumberError.TooBig) {std.debug.print(">4. ", .{});}- if (???) {+ if (number_error == MyNumberError.TooSmall) {std.debug.print("<4. ", .{});}if (number_error == MyNumberError.TooFour) {
--- exercises/020_quiz3.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/020_quiz3.zig 2023-10-05 20:04:06.932764573 +0200@@ -21,8 +21,8 @@//// This function prints, but does not return anything.//-fn printPowersOfTwo(numbers: [4]u16) ??? {- loop (numbers) |n| {+fn printPowersOfTwo(numbers: [4]u16) void {+ for (numbers) |n| {std.debug.print("{} ", .{twoToThe(n)});}}@@ -31,13 +31,13 @@// exercise. But don't be fooled! This one does the math without the aid// of the standard library!//-fn twoToThe(number: u16) ??? {+fn twoToThe(number: u16) u16 {var n: u16 = 0;var total: u16 = 1;- loop (n < number) : (n += 1) {+ while (n < number) : (n += 1) {total *= 2;}- return ???;+ return total;}
--- exercises/019_functions2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/019_functions2.zig 2023-10-05 20:04:06.926097780 +0200@@ -22,7 +22,7 @@// You'll need to figure out the parameter name and type that we're// expecting. The output type has already been specified for you.//-fn twoToThe(???) u32 {+fn twoToThe(my_number: u32) u32 {return std.math.pow(u32, 2, my_number);// std.math.pow(type, a, b) takes a numeric type and two// numbers of that type (or that can coerce to that type) and
--- exercises/018_functions.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/018_functions.zig 2023-10-05 20:04:06.922764386 +0200@@ -25,6 +25,6 @@// We're just missing a couple things. One thing we're NOT missing is the// keyword "pub", which is not needed here. Can you guess why?//-??? deepThought() ??? {+fn deepThought() u8 {return 42; // Number courtesy Douglas Adams}
--- exercises/017_quiz2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/017_quiz2.zig 2023-10-05 20:04:06.919430989 +0200@@ -9,18 +9,18 @@// Let's go from 1 to 16. This has been started for you, but there// are some problems. :-(//-const std = import standard library;+const std = @import("std");-function main() void {+pub fn main() void {var i: u8 = 1;const stop_at: u8 = 16;// What kind of loop is this? A 'for' or a 'while'?- ??? (i <= stop_at) : (i += 1) {+ while (i <= stop_at) : (i += 1) {if (i % 3 == 0) std.debug.print("Fizz", .{});if (i % 5 == 0) std.debug.print("Buzz", .{});if (!(i % 3 == 0) and !(i % 5 == 0)) {- std.debug.print("{}", .{???});+ std.debug.print("{}", .{i});}std.debug.print(", ", .{});}
--- exercises/016_for2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/016_for2.zig 2023-10-05 20:04:06.912764197 +0200@@ -25,7 +25,7 @@// the value of the place as a power of two for each bit.//// See if you can figure out the missing pieces:- for (bits, ???) |bit, ???| {+ for (bits, 0..) |bit, i| {// Note that we convert the usize i to a u32 with// @intCast(), a builtin function just like @import().// We'll learn about these properly in a later exercise.
--- exercises/015_for.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/015_for.zig 2023-10-05 20:04:06.909430803 +0200@@ -15,7 +15,7 @@std.debug.print("A Dramatic Story: ", .{});- for (???) |???| {+ for (story) |scene| {if (scene == 'h') std.debug.print(":-) ", .{});if (scene == 's') std.debug.print(":-( ", .{});if (scene == 'n') std.debug.print(":-| ", .{});
--- exercises/014_while4.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/014_while4.zig 2023-10-05 20:04:06.906097406 +0200@@ -18,7 +18,7 @@// Oh dear! This while loop will go forever?!// Please fix this so the print statement below gives the desired output.while (true) : (n += 1) {- if (???) ???;+ if (n == 4) break;}// Result: we want n=4
--- exercises/013_while3.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/013_while3.zig 2023-10-05 20:04:06.899430616 +0200@@ -24,8 +24,8 @@while (n <= 20) : (n += 1) {// The '%' symbol is the "modulo" operator and it// returns the remainder after division.- if (n % 3 == 0) ???;- if (n % 5 == 0) ???;+ if (n % 3 == 0) continue;+ if (n % 5 == 0) continue;std.debug.print("{} ", .{n});}
--- exercises/012_while2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/012_while2.zig 2023-10-05 20:04:06.896097219 +0200@@ -25,7 +25,7 @@// Please set the continue expression so that we get the desired// results in the print statement below.- while (n < 1000) : ??? {+ while (n < 1000) : (n *= 2) {// Print the current numberstd.debug.print("{} ", .{n});}
--- exercises/011_while.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/011_while.zig 2023-10-05 20:04:06.892763823 +0200@@ -21,7 +21,7 @@var n: u32 = 2;// Please use a condition that is true UNTIL "n" reaches 1024:- while (???) {+ while (n < 1024) {// Print the current numberstd.debug.print("{} ", .{n});
--- exercises/010_if2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/010_if2.zig 2023-10-05 20:04:06.886097032 +0200@@ -10,7 +10,7 @@// Please use an if...else expression to set "price".// If discount is true, the price should be $17, otherwise $20:- const price: u8 = if ???;+ const price: u8 = if (discount) 17 else 20;std.debug.print("With the discount, the price is ${}.\n", .{price});}
--- exercises/009_if.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/009_if.zig 2023-10-05 20:04:06.882763636 +0200@@ -24,7 +24,7 @@const foo = 1;// Please fix this condition:- if (foo) {+ if (foo == 1) {// We want our program to print this message!std.debug.print("Foo is 1!\n", .{});} else {
--- exercises/008_quiz.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/008_quiz.zig 2023-10-05 20:04:06.879430240 +0200@@ -19,11 +19,11 @@// the idiomatic type to use for array indexing.//// There IS a problem on this line, but 'usize' isn't it.- const x: usize = 1;+ var x: usize = 1;// Note: When you want to declare memory (an array in this// case) without putting anything in it, you can set it to- // 'undefined'. There is no problem on this line.+ // 'undefined'. There is no error here.var lang: [3]u8 = undefined;// The following lines attempt to put 'Z', 'i', and 'g' into the@@ -33,10 +33,10 @@lang[0] = letters[x];x = 3;- lang[???] = letters[x];+ lang[1] = letters[x];- x = ???;- lang[2] = letters[???];+ x = 5;+ lang[2] = letters[x];// We want to "Program in Zig!" of course:std.debug.print("Program in {s}!\n", .{lang});
--- exercises/007_strings2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/007_strings2.zig 2023-10-05 20:04:06.872763449 +0200@@ -15,9 +15,9 @@pub fn main() void {const lyrics =- Ziggy played guitar- Jamming good with Andrew Kelley- And the Spiders from Mars+ \\Ziggy played guitar+ \\Jamming good with Andrew Kelley+ \\And the Spiders from Mars;std.debug.print("{s}\n", .{lyrics});
--- exercises/006_strings.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/006_strings.zig 2023-10-05 20:04:06.869430053 +0200@@ -24,18 +24,18 @@// (Problem 1)// Use array square bracket syntax to get the letter 'd' from// the string "stardust" above.- const d: u8 = ziggy[???];+ const d: u8 = ziggy[4];// (Problem 2)// Use the array repeat '**' operator to make "ha ha ha ".- const laugh = "ha " ???;+ const laugh = "ha " ** 3;// (Problem 3)// Use the array concatenation '++' operator to make "Major Tom".// (You'll need to add a space as well!)const major = "Major";const tom = "Tom";- const major_tom = major ??? tom;+ const major_tom = major ++ " " ++ tom;// That's all the problems. Let's see our results:std.debug.print("d={u} {s}{s}\n", .{ d, laugh, major_tom });
--- exercises/005_arrays2.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/005_arrays2.zig 2023-10-05 20:04:06.862763262 +0200@@ -25,12 +25,12 @@// (Problem 1)// Please set this array concatenating the two arrays above.// It should result in: 1 3 3 7- const leet = ???;+ const leet = le ++ et;// (Problem 2)// Please set this array using repetition.// It should result in: 1 0 0 1 1 0 0 1 1 0 0 1- const bit_pattern = [_]u8{ ??? } ** 3;+ const bit_pattern = [_]u8{ 1, 0, 0, 1 } ** 3;// Okay, that's all of the problems. Let's see the results.//
--- exercises/004_arrays.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/004_arrays.zig 2023-10-05 20:04:06.859429866 +0200@@ -27,7 +27,7 @@// (Problem 1)// This "const" is going to cause a problem later - can you see what it is?// How do we fix it?- const some_primes = [_]u8{ 1, 3, 5, 7, 11, 13, 17, 19 };+ var some_primes = [_]u8{ 1, 3, 5, 7, 11, 13, 17, 19 };// Individual values can be set with '[]' notation.// Example: This line changes the first prime to 2 (which is correct):@@ -40,11 +40,11 @@// (Problem 2)// Looks like we need to complete this expression. Use the example// above to set "fourth" to the fourth element of the some_primes array:- const fourth = some_primes[???];+ const fourth = some_primes[3];// (Problem 3)// Use the len property to get the length of the array:- const length = some_primes.???;+ const length = some_primes.len;std.debug.print("First: {}, Fourth: {}, Length: {}\n", .{first, fourth, length,
--- exercises/003_assignment.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/003_assignment.zig 2023-10-05 20:04:06.856096469 +0200@@ -34,12 +34,12 @@const std = @import("std");pub fn main() void {- const n: u8 = 50;+ var n: u8 = 50;n = n + 5;- const pi: u8 = 314159;+ const pi: u32 = 314159;- const negative_eleven: u8 = -11;+ const negative_eleven: i8 = -11;// There are no errors in the next line, just explanation:// Perhaps you noticed before that the print function takes two
--- exercises/002_std.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/002_std.zig 2023-10-05 20:04:06.849429678 +0200@@ -11,7 +11,7 @@// Please complete the import below://-??? = @import("std");+const std = @import("std");pub fn main() void {std.debug.print("Standard Library.\n", .{});
--- exercises/001_hello.zig 2023-10-03 22:15:22.122241138 +0200+++ answers/001_hello.zig 2023-10-05 20:04:06.846096282 +0200@@ -16,6 +16,6 @@//const std = @import("std");-fn main() void {+pub fn main() void {std.debug.print("Hello world!\n", .{});}
#!/bin/sh## "It isn't fair, my precious, is it,# to ask us what it's got in it's# nassty little pocketsess?"# Gollum, The Hobbit, or There and Back Again#if [ ! -f 'patches/gollum.sh' ]thenecho "We must be run from the project root dir, precious!"; exit 1fiex=$(printf "%03d" $1)echo "Nassssty exercise $ex..."f=$(basename exercises/${ex}_*.zig .zig 2> /dev/null)b=exercises/$f.ziga=answers/$f.zigp=patches/patches/$f.patchif [ ! -f $b ]; then echo "No $f! We hates it!"; exit 1; fiif [ ! -f $a ]; then echo "No $a! Where is it? Where is the answer, precious?"; exit; fiecho "Hissss! before: '$b'"echo " after: '$a'"echo " patch: '$p'"diff -u $b $a > $pcat $p
#!/bin/bash## "How do you pick up the threads of an old life?# How do you go on, when in your heart you begin# to understand... there is no going back?# There are some things that time cannot mend.# Some hurts that go too deep, that have taken hold."# Frodo, The Return of the King### This script shall repair the patches for the little# broken programs using the old patches in this directory# first, to heal them and then create new and better# patches, with Gollum's help.#set -e# We check ourselves before we wreck ourselves.if [ ! -f patches/frodo.sh ]thenecho "But I must be run from the project root directory."exit 1fi# Create directory of answers if it doesn't already exist.mkdir -p answers# Cycle through all the little broken Zig applications.i=0for broken in exercises/*.zigdo((i=i+1))# Remove the dir and extension, rendering the True Name.true_name=$(basename "$broken" .zig)patch_name="patches/patches/$true_name.patch"healed_name="answers/$true_name.zig"cp "$broken" "$healed_name"# echo "$patch_name"if [ -f "$patch_name" ]then# Apply the bandages to the wounds, grow new limbs, let# new life spring into the broken bodies of the fallen.echo Healing "$true_name"...patch -i "$patch_name" "$healed_name"# Create new prescriptions...echo Repairing "$patch_name"...if [ "$true_name.patch" = "999_the_end.patch" ]theni=999fi# with gollum's help!./patches/gollum.sh $ielseecho Cannot repair "$true_name". No patch found.fidone
#!/bin/bash## "I will be a shieldmaiden no longer,# nor vie with the great Riders, nor# take joy only in the songs of slaying.# I will be a healer, and love all things# that grow and are not barren."# Éowyn, The Return of the King### This script shall heal the little broken programs# using the patches in this directory and convey them# to convalesce in the healed directory.#delete_progress() {progress_file=".progress.txt"if [ -f $progress_file ]; thenrm $progress_filefi}set -e# We check ourselves before we wreck ourselves.if [ ! -f patches/eowyn.sh ]thenecho "But I must be run from the project root directory."exit 1fi# Which version we have?echo "Zig version" $(zig version)echo "Eowyn version 25.1.9, let's try our magic power."echo ""# Remove progress filedelete_progress# Create directory of healing if it doesn't already exist.mkdir -p patches/healed# Cycle through all the little broken Zig applications.for broken in exercises/*.zigdo# Remove the dir and extension, rendering the True Name.true_name=$(basename "$broken" .zig)patch_name="patches/patches/$true_name.patch"if [ -f "$patch_name" ]then# Apply the bandages to the wounds, grow new limbs, let# new life spring into the broken bodies of the fallen.echo Healing "$true_name"...cp "$broken" "patches/healed/$true_name.zig"patch -i "$patch_name" "patches/healed/$true_name.zig"elseecho Cannot heal "$true_name". No patch found.fidoneecho "Looking for non-conforming code formatting..."zig fmt --check patches/healed# Test the healed exercises. May the compiler have mercy upon us.zig build -Dhealed# Remove progress file againdelete_progress
# No Peeking! :-)Welcome to the ziglings/patches directory. This is how ziglings is tested.The patches fix the broken exercises so that they work again, which means theanswers are here, so no peeking!## ÉowynA Bash shell script named `eowyn.sh` dwells here. She heals the little brokenprograms and places them in a `healed` directory, which is NOT committed to therepo.```bash$ patches/eowyn.sh```(If you invoke her from elsewhere, she'll come here to ply her trade.)The `build.zig` build script at the heart of Ziglings has a top-secret optionwhich tells it to test from the `patches/healed/` dir rather than `exercises/`:```bash$ zig build -Dhealed [step]```Éowyn tests all healed programs using this secret option.## GollumAnother Bash shell script named `gollum.sh` may also be found. He snatches theoriginal answers and stows them in his secret answers stash. If you leave himalone, he'll leave you alone.
// This is the end for now!// More exercises will follow...const print = @import("std").debug.print;pub fn main() void {print("\nThis is the end for now!\nWe hope you had fun and were able to learn a lot, so visit us again when the next exercises are available.\n", .{});}
// ----------------------------------------------------------------------------// 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 PORTBprint(" {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 PORTBprint(" {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 PORTBprint(" {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 PORTBprint(" {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 bitfn 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();}
// 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); // 8const 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});}
//// 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", .{});}
//// Prerequisite :// - exercise/106_files.zig, or// - create a file {project_root}/output/zigling.txt// with content `It's zigling time!`(18 byte 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 readconst std = @import("std");pub fn main() !void {// Get the current working directoryconst cwd = std.fs.cwd();// try to open ./output assuming you did your 106_files exercisevar output_dir = try cwd.openDir("output", .{});defer output_dir.close();// try to open the fileconst file = try output_dir.openFile("zigling.txt", .{});defer file.close();// 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 belowvar content = ['A']*64;// this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`std.debug.print("{s}\n", .{content});// 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.fs.File// hint: you might find two answers that are both valid in this caseconst 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});}
//// 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.fs//// In this exercise, we'll try to:// - create a new directory,// - open a file in the directory,// - write to the file.//// import std as alwaysconst std = @import("std");pub fn main() !void {// first we get the current working directoryconst cwd: std.fs.Dir = std.fs.cwd();// then we'll try to make a new directory /output/// to store our output files.cwd.makeDir("output") 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 throughelse => 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.fs.Dir = cwd.openDir("output", .{});defer output_dir.close();// we try to open the file `zigling.txt`,// and propagate any error upconst file: std.fs.File = try output_dir.createFile("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();// you are not allowed to move these two lines above the file closing line!const byte_written = try file.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 the following command// >> cat ./output/zigling.txt////// More on Creating files//// notice in:// ... try output_dir.createFile("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.fs.Dir` here:// https://ziglang.org/documentation/master/std/#std.fs.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?
//// 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.
//// 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.//// 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.//// 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.//// 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.////// 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 parameterconst 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 threadconst handle2 = try std.Thread.spawn(.{}, thread_function, .{-4}); // that can't be right?defer handle2.join();// Third threadconst 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.std.Thread.sleep(1500 * std.time.ns_per_ms);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(num: usize) !void {std.Thread.sleep(200 * num * std.time.ns_per_ms);std.debug.print("thread {d}: {s}\n", .{ num, "started." });// This timer simulates the work of the thread.const work_time = 3 * ((5 - num % 3) - 2);std.Thread.sleep(work_time * std.time.ns_per_s);std.debug.print("thread {d}: {s}\n", .{ num, "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.
//// 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 and at the latest then it will be clear to everyone 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 ally = std.testing.allocator;//// // The allocator is used to initialize an array into which// // the numbers are stored.// var list = std.ArrayList(u32).init(ally);//// // This way you can never forget what is urgently needed// // and the compiler doesn't grumble either.// defer list.deinit();//// // 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(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 inputconst 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 themvar cnt: usize = 0;while (it.next()) |word| {cnt += 1;print("{s}\n", .{word});}// print the resultprint("This little poem has {d} words!\n", .{cnt});}
//// 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(add(41, 1), 42);// 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));}
//// 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//
//// 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.
//// 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://github.com/ziglang/zig/blob/master/lib/std/fmt.zig#L33//// 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 multiplyconst 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", .{});}}
//// 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 pangramprint("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 charactersif (str.len < 26) return false;// we use a 32 bit variable of which we need 26 bitsvar bits: u32 = 0;// loop about all characters in the stringfor (str) |c| {// if the character is an alphabetical characterif (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 bitbits |= @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..???;}
//// 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 yvar x: u8 = 0b1101;var y: u8 = 0b1011;// Now we swap the values of the two variables by doing xor on themx ^= 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.
//// 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 inputconst arr: []const f64 = &[_]f64{ 0.3, 0.2, 0.1, 0.1, 0.4 };// initialize the allocatorvar arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);// free the memory on exitdefer arena.deinit();// initialize the allocatorconst allocator = arena.allocator();// allocate memory for this arrayconst 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
//// 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//// 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", .{});}//// 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'.
//// 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 placesstd.debug.print("The normalized angle of {d: >3.1} degrees is {d: >3.1} degrees.\n", .{ angle, result });}
//// 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 Zigconst std = @import("std");// and here the new import for Cconst 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".//
//// 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.
//// 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", .{});}
//// 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.//
//// 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";}
//// 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.";}
//// 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) {??????}}
//// 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 {}}}
//// 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", .{});}
//// 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", .{});}
//// Anonymous struct literal syntax can also be used to compose an// "anonymous list" with an array type destination://// const foo: [3]u32 = .{10, 20, 30};//// Otherwise it's a "tuple"://// const bar = .{10, 20, 30};//// The only difference is the destination type.//const print = @import("std").debug.print;pub fn main() void {// Please make 'hello' a string-like array of u8 WITHOUT// changing the value literal.//// Don't change this part://// = .{ 'h', 'e', 'l', 'l', 'o' };//const hello = .{ 'h', 'e', 'l', 'l', 'o' };print("I say {s}!\n", .{hello});}
//// You can even create anonymous struct literals without field// names://// .{// false,// @as(u32, 15),// @as(f64, 67.12)// }//// We call these "tuples", which is a term used by many// programming languages for a data type with fields referenced// by index order rather than name. To make this possible, the Zig// compiler automatically assigns numeric field names 0, 1, 2,// etc. to the struct.//// Since bare numbers are not legal identifiers (foo.0 is a// syntax error), we have to quote them with the @"" syntax.// Example://// const foo = .{ true, false };//// print("{} {}\n", .{foo.@"0", foo.@"1"});//// The example above prints "true false".//// Hey, WAIT A SECOND...//// If a .{} thing is what the print function wants, do we need to// break our "tuple" apart and put it in another one? No! It's// redundant! This will print the same thing://// print("{} {}\n", foo);//// Aha! So now we know that print() takes a "tuple". Things are// really starting to come together now.//const print = @import("std").debug.print;pub fn main() void {// A "tuple":const foo = .{true,false,@as(i32, 42),@as(f32, 3.141592),};// We'll be implementing this:printTuple(foo);// This is just for fun, because we can:const nothing = .{};print("\n", nothing);}// Let's make our own generic "tuple" printer. This should take a// "tuple" and print out each field in the following format://// "name"(type):value//// Example://// "0"(bool):true//// You'll be putting this together. But don't worry, everything// you need is documented in the comments.fn printTuple(tuple: anytype) void {// 1. Get a list of fields in the input 'tuple'// parameter. You'll need://// @TypeOf() - takes a value, returns its type.//// @typeInfo() - takes a type, returns a TypeInfo union// with fields specific to that type.//// The list of a struct type's fields can be found in// TypeInfo's @"struct".fields.//// Example://// @typeInfo(Circle).@"struct".fields//// This will be an array of StructFields.const fields = ???;// 2. Loop through each field. This must be done at compile// time.//// Hint: remember 'inline' loops?//for (fields) |field| {// 3. Print the field's name, type, and value.//// Each 'field' in this loop is one of these://// pub const StructField = struct {// name: [:0]const u8,// type: type,// default_value_ptr: ?*const anyopaque,// is_comptime: bool,// alignment: comptime_int,// };//// Note we will learn about 'anyopaque' type later//// You'll need this builtin://// @field(lhs: anytype, comptime field_name: []const u8)//// The first parameter is the value to be accessed,// the second parameter is a string with the name of// the field you wish to access. The value of the// field is returned.//// Example://// @field(foo, "x"); // returns the value at foo.x//// The first field should print as: "0"(bool):trueprint("\"{s}\"({any}):{any} ", .{field.???,field.???,???,});}}
//// An anonymous struct value LITERAL (not to be confused with a// struct TYPE) uses '.{}' syntax://// .{// .center_x = 15,// .center_y = 12,// .radius = 6,// }//// These literals are always evaluated entirely at compile-time.// The example above could be coerced into the i32 variant of the// "circle struct" from the last exercise.//// Or you can let them remain entirely anonymous as in this// example://// fn bar(foo: anytype) void {// print("a:{} b:{}\n", .{foo.a, foo.b});// }//// bar(.{// .a = true,// .b = false,// });//// The example above prints "a:true b:false".//const print = @import("std").debug.print;pub fn main() void {printCircle(.{.center_x = @as(u32, 205),.center_y = @as(u32, 187),.radius = @as(u32, 12),});}// Please complete this function which prints an anonymous struct// representing a circle.fn printCircle(???) void {print("x:{} y:{} radius:{}\n", .{circle.center_x,circle.center_y,circle.radius,});}
//// Struct types are always "anonymous" until we give them a name://// struct {};//// So far, we've been giving struct types a name like so://// const Foo = struct {};//// * The value of @typeName(Foo) is "<filename>.Foo".//// A struct is also given a name when you return it from a// function://// fn Bar() type {// return struct {};// }//// const MyBar = Bar(); // store the struct type// const bar = Bar() {}; // create instance of the struct//// * The value of @typeName(Bar()) is "<filename>.Bar()".// * The value of @typeName(MyBar) is "<filename>.Bar()".// * The value of @typeName(@TypeOf(bar)) is "<filename>.Bar()".//// You can also have completely anonymous structs. The value// of @typeName(struct {}) is "<filename>.<function>__struct_<nnn>".//const print = @import("std").debug.print;// This function creates a generic data structure by returning an// anonymous struct type (which will no longer be anonymous AFTER// it's returned from the function).fn Circle(comptime T: type) type {return struct {center_x: T,center_y: T,radius: T,};}pub fn main() void {//// See if you can complete these two variable initialization// expressions to create instances of circle struct types// which can hold these values://// * circle1 should hold i32 integers// * circle2 should hold f32 floats//const circle1 = ??? {.center_x = 25,.center_y = 70,.radius = 15,};const circle2 = ??? {.center_x = 25.234,.center_y = 70.999,.radius = 15.714,};print("[{s}: {},{},{}] ", .{stripFname(@typeName(@TypeOf(circle1))),circle1.center_x,circle1.center_y,circle1.radius,});print("[{s}: {d:.1},{d:.1},{d:.1}]\n", .{stripFname(@typeName(@TypeOf(circle2))),circle2.center_x,circle2.center_y,circle2.radius,});}// Perhaps you remember the "narcissistic fix" for the type name// in Ex. 065? We're going to do the same thing here: use a hard-// coded slice to return the type name. That's just so our output// looks prettier. Indulge your vanity. Programmers are beautiful.fn stripFname(mytype: []const u8) []const u8 {return mytype[22..];}// The above would be an instant red flag in a "real" program.
//// Sometimes you need to create an identifier that will not, for// whatever reason, play by the naming rules://// const 55_cows: i32 = 55; // ILLEGAL: starts with a number// const isn't true: bool = false; // ILLEGAL: what even?!//// If you try to create either of these under normal// circumstances, a special Program Identifier Syntax Security// Team (PISST) will come to your house and take you away.//// Thankfully, Zig has a way to sneak these wacky identifiers// past the authorities: the @"" identifier quoting syntax.//// @"foo"//// Please help us safely smuggle these fugitive identifiers into// our program://const print = @import("std").debug.print;pub fn main() void {const 55_cows: i32 = 55;const isn't true: bool = false;print("Sweet freedom: {}, {}.\n", .{55_cows,isn't true,});}
//// We were able to get a printable string out of a many-item// pointer by using a slice to assert a specific length.//// But can we ever GO BACK to a sentinel-terminated pointer// after we've "lost" the sentinel in a coercion?//// Yes, we can. Zig's @ptrCast() builtin can do this. Check out// the signature://// @ptrCast(value: anytype) anytype//// See if you can use it to solve the same many-item pointer// problem, but without needing a length!//const print = @import("std").debug.print;pub fn main() void {// Again, we've coerced the sentinel-terminated string to a// many-item pointer, which has no length or sentinel.const data: [*]const u8 = "Weird Data!";// Please cast 'data' to 'printable':const printable: [*:0]const u8 = ???;print("{s}\n", .{printable});}
//// ------------------------------------------------------------// TOP SECRET TOP SECRET TOP SECRET TOP SECRET TOP SECRET// ------------------------------------------------------------//// Are you ready for the THE TRUTH about Zig string literals?//// Here it is://// @TypeOf("foo") == *const [3:0]u8//// Which means a string literal is a "constant pointer to a// zero-terminated (null-terminated) fixed-size array of u8".//// Now you know. You've earned it. Welcome to the secret club!//// ------------------------------------------------------------//// Why do we bother using a zero/null sentinel to terminate// strings in Zig when we already have a known length?//// Versatility! Zig strings are compatible with C strings (which// are null-terminated) AND can be coerced to a variety of other// Zig types://// const a: [5]u8 = "array".*;// const b: *const [16]u8 = "pointer to array";// const c: []const u8 = "slice";// const d: [:0]const u8 = "slice with sentinel";// const e: [*:0]const u8 = "many-item pointer with sentinel";// const f: [*]const u8 = "many-item pointer";//// All but 'f' may be printed. (A many-item pointer without a// sentinel is not safe to print because we don't know where it// ends!)//const print = @import("std").debug.print;const WeirdContainer = struct {data: [*]const u8,length: usize,};pub fn main() void {// WeirdContainer is an awkward way to house a string.//// Being a many-item pointer (with no sentinel termination),// the 'data' field "loses" the length information AND the// sentinel termination of the string literal "Weird Data!".//// Luckily, the 'length' field makes it possible to still// work with this value.const foo = WeirdContainer{.data = "Weird Data!",.length = 11,};// How do we get a printable value from 'foo'? One way is to// turn it into something with a known length. We do have a// length... You've actually solved this problem before!//// Here's a big hint: do you remember how to take a slice?const printable = ???;print("{s}\n", .{printable});}
//// A sentinel value indicates the end of data. Let's imagine a// sequence of lowercase letters where uppercase 'S' is the// sentinel, indicating the end of the sequence://// abcdefS//// If our sequence also allows for uppercase letters, 'S' would// make a terrible sentinel since it could no longer be a regular// value in the sequence://// abcdQRST// ^-- Oops! The last letter in the sequence is R!//// A popular choice for indicating the end of a string is the// value 0. ASCII and Unicode call this the "Null Character".//// Zig supports sentinel-terminated arrays, slices, and pointers://// const a: [4:0]u32 = [4:0]u32{1, 2, 3, 4};// const b: [:0]const u32 = &[4:0]u32{1, 2, 3, 4};// const c: [*:0]const u32 = &[4:0]u32{1, 2, 3, 4};//// Array 'a' stores 5 u32 values, the last of which is 0.// However the compiler takes care of this housekeeping detail// for you. You can treat 'a' as a normal array with just 4// items.//// Slice 'b' is only allowed to point to zero-terminated arrays// but otherwise works just like a normal slice.//// Pointer 'c' is exactly like the many-item pointers we learned// about in exercise 054, but it is guaranteed to end in 0.// Because of this guarantee, we can safely find the end of this// many-item pointer without knowing its length. (We CAN'T do// that with regular many-item pointers!).//// Important: the sentinel value must be of the same type as the// data being terminated!//const print = @import("std").debug.print;const sentinel = @import("std").meta.sentinel;pub fn main() void {// Here's a zero-terminated array of u32 values:var nums = [_:0]u32{ 1, 2, 3, 4, 5, 6 };// And here's a zero-terminated many-item pointer:const ptr: [*:0]u32 = &nums;// For fun, let's replace the value at position 3 with the// sentinel value 0. This seems kind of naughty.nums[3] = 0;// So now we have a zero-terminated array and a many-item// pointer that reference the same data: a sequence of// numbers that both ends in and CONTAINS the sentinel value.//// Attempting to loop through and print both of these should// demonstrate how they are similar and different.//// (It turns out that the array prints completely, including// the sentinel 0 in the middle. The many-item pointer stops// at the first sentinel value.)printSequence(nums);printSequence(ptr);print("\n", .{});}// Here's our generic sequence printing function. It's nearly// complete, but there are a couple of missing bits. Please fix// them!fn printSequence(my_seq: anytype) void {const my_typeinfo = @typeInfo(@TypeOf(my_seq));// The TypeInfo contained in my_typeinfo is a union. We use// a switch to handle printing the Array or Pointer fields,// depending on which type of my_seq was passed in:switch (my_typeinfo) {.array => {print("Array:", .{});// Loop through the items in my_seq.for (???) |s| {print("{}", .{s});}},.pointer => {// Check this out - it's pretty cool:const my_sentinel = sentinel(@TypeOf(my_seq));print("Many-item pointer:", .{});// Loop through the items in my_seq until we hit the// sentinel value.var i: usize = 0;while (??? != my_sentinel) {print("{}", .{my_seq[i]});i += 1;}},else => unreachable,}print(". ", .{});}
//// Quiz Time!//// Let's revisit the Hermit's Map from Quiz 7.//// Oh, don't worry, it's not nearly as big without all the// explanatory comments. And we're only going to change one part// of it.//const print = @import("std").debug.print;const TripError = error{ Unreachable, EatenByAGrue };const Place = struct {name: []const u8,paths: []const Path = undefined,};var a = Place{ .name = "Archer's Point" };var b = Place{ .name = "Bridge" };var c = Place{ .name = "Cottage" };var d = Place{ .name = "Dogwood Grove" };var e = Place{ .name = "East Pond" };var f = Place{ .name = "Fox Pond" };// Remember how we didn't have to declare the numeric type of the// place_count because it is only used at compile time? That// probably makes a lot more sense now. :-)const place_count = 6;const Path = struct {from: *const Place,to: *const Place,dist: u8,};// Okay, so as you may recall, we had to create each Path struct// by hand and each one took 5 lines of code to define://// Path{// .from = &a, // from: Archer's Point// .to = &b, // to: Bridge// .dist = 2,// },//// Well, armed with the knowledge that we can run code at compile// time, we can perhaps shorten this a bit with a simple function// instead.//// Please fill in the body of this function!fn makePath(from: *Place, to: *Place, dist: u8) Path {}// Using our new function, these path definitions take up considerably less// space in our program now!const a_paths = [_]Path{makePath(&a, &b, 2)};const b_paths = [_]Path{ makePath(&b, &a, 2), makePath(&b, &d, 1) };const c_paths = [_]Path{ makePath(&c, &d, 3), makePath(&c, &e, 2) };const d_paths = [_]Path{ makePath(&d, &b, 1), makePath(&d, &c, 3), makePath(&d, &f, 7) };const e_paths = [_]Path{ makePath(&e, &c, 2), makePath(&e, &f, 1) };const f_paths = [_]Path{makePath(&f, &d, 7)};//// But is it more readable? That could be argued either way.//// We've seen that it is possible to parse strings at compile// time, so the sky's really the limit on how fancy we could get// with this.//// For example, we could create our own "path language" and// create Paths from that. Something like this, perhaps://// a -> (b[2])// b -> (a[2] d[1])// c -> (d[3] e[2])// ...//// Feel free to implement something like that as a SUPER BONUS EXERCISE!const TripItem = union(enum) {place: *const Place,path: *const Path,fn printMe(self: TripItem) void {switch (self) {.place => |p| print("{s}", .{p.name}),.path => |p| print("--{}->", .{p.dist}),}}};const NotebookEntry = struct {place: *const Place,coming_from: ?*const Place,via_path: ?*const Path,dist_to_reach: u16,};const HermitsNotebook = struct {entries: [place_count]?NotebookEntry = .{null} ** place_count,next_entry: u8 = 0,end_of_entries: u8 = 0,fn getEntry(self: *HermitsNotebook, place: *const Place) ?*NotebookEntry {for (&self.entries, 0..) |*entry, i| {if (i >= self.end_of_entries) break;if (place == entry.*.?.place) return &entry.*.?;}return null;}fn checkNote(self: *HermitsNotebook, note: NotebookEntry) void {const existing_entry = self.getEntry(note.place);if (existing_entry == null) {self.entries[self.end_of_entries] = note;self.end_of_entries += 1;} else if (note.dist_to_reach < existing_entry.?.dist_to_reach) {existing_entry.?.* = note;}}fn hasNextEntry(self: *HermitsNotebook) bool {return self.next_entry < self.end_of_entries;}fn getNextEntry(self: *HermitsNotebook) *const NotebookEntry {defer self.next_entry += 1;return &self.entries[self.next_entry].?;}fn getTripTo(self: *HermitsNotebook, trip: []?TripItem, dest: *Place) TripError!void {const destination_entry = self.getEntry(dest);if (destination_entry == null) {return TripError.Unreachable;}var current_entry = destination_entry.?;var i: u8 = 0;while (true) : (i += 2) {trip[i] = TripItem{ .place = current_entry.place };if (current_entry.coming_from == null) break;trip[i + 1] = TripItem{ .path = current_entry.via_path.? };const previous_entry = self.getEntry(current_entry.coming_from.?);if (previous_entry == null) return TripError.EatenByAGrue;current_entry = previous_entry.?;}}};pub fn main() void {const start = &a; // Archer's Pointconst destination = &f; // Fox Pond// We could either have this://// a.paths = a_paths[0..];// b.paths = b_paths[0..];// c.paths = c_paths[0..];// d.paths = d_paths[0..];// e.paths = e_paths[0..];// f.paths = f_paths[0..];//// or this comptime wizardry://const letters = [_][]const u8{ "a", "b", "c", "d", "e", "f" };inline for (letters) |letter| {@field(@This(), letter).paths = @field(@This(), letter ++ "_paths")[0..];}var notebook = HermitsNotebook{};var working_note = NotebookEntry{.place = start,.coming_from = null,.via_path = null,.dist_to_reach = 0,};notebook.checkNote(working_note);while (notebook.hasNextEntry()) {const place_entry = notebook.getNextEntry();for (place_entry.place.paths) |*path| {working_note = NotebookEntry{.place = path.to,.coming_from = place_entry.place,.via_path = path,.dist_to_reach = place_entry.dist_to_reach + path.dist,};notebook.checkNote(working_note);}}var trip = [_]?TripItem{null} ** (place_count * 2);notebook.getTripTo(trip[0..], destination) catch |err| {print("Oh no! {}\n", .{err});return;};printTrip(trip[0..]);}fn printTrip(trip: []?TripItem) void {var i: u8 = @intCast(trip.len);while (i > 0) {i -= 1;if (trip[i] == null) continue;trip[i].?.printMe();}print("\n", .{});}
//// In addition to knowing when to use the 'comptime' keyword,// it's also good to know when you DON'T need it.//// The following contexts are already IMPLICITLY evaluated at// compile time, and adding the 'comptime' keyword would be// superfluous, redundant, and smelly://// * The container-level scope (outside of any function in a source file)// * Type declarations of:// * Variables// * Functions (types of parameters and return values)// * Structs// * Unions// * Enums// * The test expressions in inline for and while loops// * An expression passed to the @cImport() builtin//// Work with Zig for a while, and you'll start to develop an// intuition for these contexts. Let's work on that now.//// You have been given just one 'comptime' statement to use in// the program below. Here it is://// comptime//// Just one is all it takes. Use it wisely!//const print = @import("std").debug.print;// Being in the container-level scope, everything about this value is// implicitly required to be known compile time.const llama_count = 5;// Again, this value's type and size must be known at compile// time, but we're letting the compiler infer both from the// return type of a function.const llamas = makeLlamas(llama_count);// And here's the function. Note that the return value type// depends on one of the input arguments!fn makeLlamas(count: usize) [count]u8 {var temp: [count]u8 = undefined;var i = 0;// Note that this does NOT need to be an inline 'while'.while (i < count) : (i += 1) {temp[i] = i;}return temp;}pub fn main() void {print("My llama value is {}.\n", .{llamas[2]});}//// The lesson here is to not pepper your program with 'comptime'// keywords unless you need them. Between the implicit compile// time contexts and Zig's aggressive evaluation of any// expression it can figure out at compile time, it's sometimes// surprising how few places actually need the keyword.
//// As a matter of fact, you can put 'comptime' in front of any// expression to force it to be run at compile time.//// Execute a function://// comptime llama();//// Get a value://// bar = comptime baz();//// Execute a whole block://// comptime {// bar = baz + biff();// llama(bar);// }//// Get a value from a block://// var llama = comptime bar: {// const baz = biff() + bonk();// break :bar baz;// }//const print = @import("std").debug.print;const llama_count = 5;const llamas = [llama_count]u32{ 5, 10, 15, 20, 25 };pub fn main() void {// We meant to fetch the last llama. Please fix this simple// mistake so the assertion no longer fails.const my_llama = getLlama(5);print("My llama value is {}.\n", .{my_llama});}fn getLlama(i: usize) u32 {// We've put a guard assert() at the top of this function to// prevent mistakes. The 'comptime' keyword here means that// the mistake will be caught when we compile!//// Without 'comptime', this would still work, but the// assertion would fail at runtime with a PANIC, and that's// not as nice.//// Unfortunately, we're going to get an error right now// because the 'i' parameter needs to be guaranteed to be// known at compile time. What can you do with the 'i'// parameter above to make this so?comptime assert(i < llama_count);return llamas[i];}// Fun fact: this assert() function is identical to// std.debug.assert() from the Zig Standard Library.fn assert(ok: bool) void {if (!ok) unreachable;}//// Bonus fun fact: I accidentally replaced all instances of 'foo'// with 'llama' in this exercise and I have no regrets!
//// There is also an 'inline while'. Just like 'inline for', it// loops at compile time, allowing you to do all sorts of// interesting things not possible at runtime. See if you can// figure out what this rather bonkers example prints://// const foo = [3]*const [5]u8{ "~{s}~", "<{s}>", "d{s}b" };// comptime var i = 0;//// inline while ( i < foo.len ) : (i += 1) {// print(foo[i] ++ "\n", .{foo[i]});// }//// You haven't taken off that wizard hat yet, have you?//const print = @import("std").debug.print;pub fn main() void {// Here is a string containing a series of arithmetic// operations and single-digit decimal values. Let's call// each operation and digit pair an "instruction".const instructions = "+3 *5 -2 *2";// Here is a u32 variable that will keep track of our current// value in the program at runtime. It starts at 0, and we// will get the final value by performing the sequence of// instructions above.var value: u32 = 0;// This "index" variable will only be used at compile time in// our loop.comptime var i = 0;// Here we wish to loop over each "instruction" in the string// at compile time.//// Please fix this to loop once per "instruction":??? (i < instructions.len) : (???) {// This gets the digit from the "instruction". Can you// figure out why we subtract '0' from it?const digit = instructions[i + 1] - '0';// This 'switch' statement contains the actual work done// at runtime. At first, this doesn't seem exciting...switch (instructions[i]) {'+' => value += digit,'-' => value -= digit,'*' => value *= digit,else => unreachable,}// ...But it's quite a bit more exciting than it first appears.// The 'inline while' no longer exists at runtime and neither// does anything else not touched directly by runtime// code. The 'instructions' string, for example, does not// appear anywhere in the compiled program because it's// not used by it!//// So in a very real sense, this loop actually converts// the instructions contained in a string into runtime// code at compile time. Guess we're compiler writers// now. See? The wizard hat was justified after all.}print("{}\n", .{value});}
//// There have been several instances where it would have been// nice to use loops in our programs, but we couldn't because the// things we were trying to do could only be done at compile// time. We ended up having to do those things MANUALLY, like// NORMAL people. Bah! We are PROGRAMMERS! The computer should be// doing this work.//// An 'inline for' is performed at compile time, allowing you to// programmatically loop through a series of items in situations// like those mentioned above where a regular runtime 'for' loop// wouldn't be allowed://// inline for (.{ u8, u16, u32, u64 }) |T| {// print("{} ", .{@typeInfo(T).Int.bits});// }//// In the above example, we're looping over a list of types,// which are available only at compile time.//const print = @import("std").debug.print;// Remember Narcissus from exercise 065 where we used builtins// for reflection? He's back and loving it.const Narcissus = struct {me: *Narcissus = undefined,myself: *Narcissus = undefined,echo: void = undefined,};pub fn main() void {print("Narcissus has room in his heart for:", .{});// Last time we examined the Narcissus struct, we had to// manually access each of the three fields. Our 'if'// statement was repeated three times almost verbatim. Yuck!//// Please use an 'inline for' to implement the block below// for each field in the slice 'fields'!const fields = @typeInfo(Narcissus).@"struct".fields;??? {if (field.type != void) {print(" {s}", .{field.name});}}// Once you've got that, go back and take a look at exercise// 065 and compare what you've written to the abomination we// had there!print(".\n", .{});}
//// Being able to pass types to functions at compile time lets us// generate code that works with multiple types. But it doesn't// help us pass VALUES of different types to a function.//// For that, we have the 'anytype' placeholder, which tells Zig// to infer the actual type of a parameter at compile time.//// fn foo(thing: anytype) void { ... }//// Then we can use builtins such as @TypeOf(), @typeInfo(),// @typeName(), @hasDecl(), and @hasField() to determine more// about the type that has been passed in. All of this logic will// be performed entirely at compile time.//const print = @import("std").debug.print;// Let's define three structs: Duck, RubberDuck, and Duct. Notice// that Duck and RubberDuck both contain waddle() and quack()// methods declared in their namespace (also known as "decls").const Duck = struct {eggs: u8,loudness: u8,location_x: i32 = 0,location_y: i32 = 0,fn waddle(self: *Duck, x: i16, y: i16) void {self.location_x += x;self.location_y += y;}fn quack(self: Duck) void {if (self.loudness < 4) {print("\"Quack.\" ", .{});} else {print("\"QUACK!\" ", .{});}}};const RubberDuck = struct {in_bath: bool = false,location_x: i32 = 0,location_y: i32 = 0,fn waddle(self: *RubberDuck, x: i16, y: i16) void {self.location_x += x;self.location_y += y;}fn quack(self: RubberDuck) void {// Assigning an expression to '_' allows us to safely// "use" the value while also ignoring it._ = self;print("\"Squeek!\" ", .{});}fn listen(self: RubberDuck, dev_talk: []const u8) void {// Listen to developer talk about programming problem.// Silently contemplate problem. Emit helpful sound._ = dev_talk;self.quack();}};const Duct = struct {diameter: u32,length: u32,galvanized: bool,connection: ?*Duct = null,fn connect(self: *Duct, other: *Duct) !void {if (self.diameter == other.diameter) {self.connection = other;} else {return DuctError.UnmatchedDiameters;}}};const DuctError = error{UnmatchedDiameters};pub fn main() void {// This is a real duck!const ducky1 = Duck{.eggs = 0,.loudness = 3,};// This is not a real duck, but it has quack() and waddle()// abilities, so it's still a "duck".const ducky2 = RubberDuck{.in_bath = false,};// This is not even remotely a duck.const ducky3 = Duct{.diameter = 17,.length = 165,.galvanized = true,};print("ducky1: {}, ", .{isADuck(ducky1)});print("ducky2: {}, ", .{isADuck(ducky2)});print("ducky3: {}\n", .{isADuck(ducky3)});}// This function has a single parameter which is inferred at// compile time. It uses builtins @TypeOf() and @hasDecl() to// perform duck typing ("if it walks like a duck and it quacks// like a duck, then it must be a duck") to determine if the type// is a "duck".fn isADuck(possible_duck: anytype) bool {// We'll use @hasDecl() to determine if the type has// everything needed to be a "duck".//// In this example, 'has_increment' will be true if type Foo// has an increment() method://// const has_increment = @hasDecl(Foo, "increment");//// Please make sure MyType has both waddle() and quack()// methods:const MyType = @TypeOf(possible_duck);const walks_like_duck = ???;const quacks_like_duck = ???;const is_duck = walks_like_duck and quacks_like_duck;if (is_duck) {// We also call the quack() method here to prove that Zig// allows us to perform duck actions on anything// sufficiently duck-like.//// Because all of the checking and inference is performed// at compile time, we still have complete type safety:// attempting to call the quack() method on a struct that// doesn't have it (like Duct) would result in a compile// error, not a runtime panic or crash!possible_duck.quack();}return is_duck;}
//// One of the more common uses of 'comptime' function parameters is// passing a type to a function://// fn foo(comptime MyType: type) void { ... }//// In fact, types are ONLY available at compile time, so the// 'comptime' keyword is required here.//// Please take a moment to put on the wizard hat which has been// provided for you. We're about to use this ability to implement// a generic function.//const print = @import("std").debug.print;pub fn main() void {// Here we declare arrays of three different types and sizes// at compile time from a function call. Neat!const s1 = makeSequence(u8, 3); // creates a [3]u8const s2 = makeSequence(u32, 5); // creates a [5]u32const s3 = makeSequence(i64, 7); // creates a [7]i64print("s1={any}, s2={any}, s3={any}\n", .{ s1, s2, s3 });}// This function is pretty wild because it executes at runtime// and is part of the final compiled program. The function is// compiled with unchanging data sizes and types.//// And yet it ALSO allows for different sizes and types. This// seems paradoxical. How could both things be true?//// To accomplish this, the Zig compiler actually generates a// separate copy of the function for every size/type combination!// So in this case, three different functions will be generated// for you, each with machine code that handles that specific// data size and type.//// Please fix this function so that the 'size' parameter://// 1) Is guaranteed to be known at compile time.// 2) Sets the size of the array of type T (which is the// sequence we're creating and returning).//fn makeSequence(comptime T: type, ??? size: usize) [???]T {var sequence: [???]T = undefined;var i: usize = 0;while (i < size) : (i += 1) {sequence[i] = @as(T, @intCast(i)) + 1;}return sequence;}
//// You can also put 'comptime' before a function parameter to// enforce that the argument passed to the function must be known// at compile time. We've actually been using a function like// this the entire time, std.debug.print()://// fn print(comptime fmt: []const u8, args: anytype) void//// Notice that the format string parameter 'fmt' is marked as// 'comptime'. One of the neat benefits of this is that the// format string can be checked for errors at compile time rather// than crashing at runtime.//// (The actual formatting is done by std.fmt.format() and it// contains a complete format string parser that runs entirely at// compile time!)//const print = @import("std").debug.print;// This struct is the model of a model boat. We can transform it// to any scale we would like: 1:2 is half-size, 1:32 is// thirty-two times smaller than the real thing, and so forth.const Schooner = struct {name: []const u8,scale: u32 = 1,hull_length: u32 = 143,bowsprit_length: u32 = 34,mainmast_height: u32 = 95,fn scaleMe(self: *Schooner, comptime scale: u32) void {comptime var my_scale = scale;// We did something neat here: we've anticipated the// possibility of accidentally attempting to create a// scale of 1:0. Rather than having this result in a// divide-by-zero error at runtime, we've turned this// into a compile error.//// This is probably the correct solution most of the// time. But our model boat model program is very casual// and we just want it to "do what I mean" and keep// working.//// Please change this so that it sets a 0 scale to 1// instead.if (my_scale == 0) @compileError("Scale 1:0 is not valid!");self.scale = my_scale;self.hull_length /= my_scale;self.bowsprit_length /= my_scale;self.mainmast_height /= my_scale;}fn printMe(self: Schooner) void {print("{s} (1:{}, {} x {})\n", .{self.name,self.scale,self.hull_length,self.mainmast_height,});}};pub fn main() void {var whale = Schooner{ .name = "Whale" };var shark = Schooner{ .name = "Shark" };var minnow = Schooner{ .name = "Minnow" };// Hey, we can't just pass this runtime variable as an// argument to the scaleMe() method. What would let us do// that?var scale: u32 = undefined;scale = 32; // 1:32 scaleminnow.scaleMe(scale);minnow.printMe();scale -= 16; // 1:16 scaleshark.scaleMe(scale);shark.printMe();scale -= 16; // 1:0 scale (oops, but DON'T FIX THIS!)whale.scaleMe(scale);whale.printMe();}//// Going deeper://// What would happen if you DID attempt to build a model in the// scale of 1:0?//// A) You're already done!// B) You would suffer a mental divide-by-zero error.// C) You would construct a singularity and destroy the// planet.//// And how about a model in the scale of 0:1?//// A) You're already done!// B) You'd arrange nothing carefully into the form of the// original nothing but infinitely larger.// C) You would construct a singularity and destroy the// planet.//// Answers can be found on the back of the Ziglings packaging.
//// We've seen that Zig implicitly performs some evaluations at// compile time. But sometimes you'll want to explicitly request// compile time evaluation. For that, we have a new keyword://// . . . o . . * . . .// . * | . . . . . . * . .// --o-- comptime * | .. .// * | * . . . . --*-- . * .// . . . . . . . . . | . . .//// When placed before a variable declaration, 'comptime'// guarantees that every usage of that variable will be performed// at compile time.//// As a simple example, compare these two statements://// var bar1 = 5; // ERROR!// comptime var bar2 = 5; // OKAY!//// The first one gives us an error because Zig assumes mutable// identifiers (declared with 'var') will be used at runtime and// we have not assigned a runtime type (like u8 or f32). Trying// to use a comptime_int of undetermined size at runtime is// a MEMORY CRIME and you are UNDER ARREST.//// The second one is okay because we've told Zig that 'bar2' is// a compile time variable. Zig will help us ensure this is true// and let us know if we make a mistake.//const print = @import("std").debug.print;pub fn main() void {//// In this contrived example, we've decided to allocate some// arrays using a variable count! But something's missing...//var count = 0;count += 1;const a1: [count]u8 = .{'A'} ** count;count += 1;const a2: [count]u8 = .{'B'} ** count;count += 1;const a3: [count]u8 = .{'C'} ** count;count += 1;const a4: [count]u8 = .{'D'} ** count;print("{s} {s} {s} {s}\n", .{ a1, a2, a3, a4 });// Builtin BONUS!//// The @compileLog() builtin is like a print statement that// ONLY operates at compile time. The Zig compiler treats// @compileLog() calls as errors, so you'll want to use them// temporarily to debug compile time logic.//// Try uncommenting this line and playing around with it// (copy it, move it) to see what it does://@compileLog("Count at compile time: ", count);}
//// "Compile time" is a program's environment while it is being// compiled. In contrast, "run time" is the environment while the// compiled program is executing (traditionally as machine code// on a hardware CPU).//// Errors make an easy example://// 1. Compile-time error: caught by the compiler, usually// resulting in a message to the programmer.//// 2. Runtime error: either caught by the running program itself// or by the host hardware or operating system. Could be// gracefully caught and handled or could cause the computer// to crash (or halt and catch fire)!//// All compiled languages must perform a certain amount of logic// at compile time in order to analyze the code, maintain a table// of symbols (such as variable and function names), etc.//// Optimizing compilers can also figure out how much of a program// can be pre-computed or "inlined" at compile time to make the// resulting program more efficient. Smart compilers can even// "unroll" loops, turning their logic into a fast linear// sequence of statements (at the usually very slight expense of// the increased size of the repeated code).//// Zig takes these concepts further by making these optimizations// an integral part of the language itself!//const print = @import("std").debug.print;pub fn main() void {// ALL numeric literals in Zig are of type comptime_int or// comptime_float. They are of arbitrary size (as big or// little as you need).//// Notice how we don't have to specify a size like "u8",// "i32", or "f64" when we assign identifiers immutably with// "const".//// When we use these identifiers in our program, the VALUES// are inserted at compile time into the executable code. The// IDENTIFIERS "const_int" and "const_float" don't exist in// our compiled application!const const_int = 12345;const const_float = 987.654;print("Immutable: {}, {d:.3}; ", .{ const_int, const_float });// But something changes when we assign the exact same values// to identifiers mutably with "var".//// The literals are STILL comptime_int and comptime_float,// but we wish to assign them to identifiers which are// mutable at runtime.//// To be mutable at runtime, these identifiers must refer to// areas of memory. In order to refer to areas of memory, Zig// must know exactly how much memory to reserve for these// values. Therefore, it follows that we just specify numeric// types with specific sizes. The comptime numbers will be// coerced (if they'll fit!) into your chosen runtime types.// For this it is necessary to specify a size, e.g. 32 bit.var var_int = 12345;var var_float = 987.654;// We can change what is stored at the areas set aside for// "var_int" and "var_float" in the running compiled program.var_int = 54321;var_float = 456.789;print("Mutable: {}, {d:.3}; ", .{ var_int, var_float });// Bonus: Now that we're familiar with Zig's builtins, we can// also inspect the types to see what they are, no guessing// needed!print("Types: {}, {}, {}, {}\n", .{@TypeOf(const_int),@TypeOf(const_float),@TypeOf(var_int),@TypeOf(var_float),});}
//// Zig has builtins for mathematical operations such as...//// @sqrt @sin @cos// @exp @log @floor//// ...and lots of type casting operations such as...//// @as @errorFromInt @floatFromInt// @ptrFromInt @intFromPtr @intFromEnum//// Spending part of a rainy day skimming through the complete// list of builtins in the official Zig documentation wouldn't be// a bad use of your time. There are some seriously cool features// in there. Check out @call, @compileLog, @embedFile, and @src!//// ...//// For now, we're going to complete our examination of builtins// by exploring just THREE of Zig's MANY introspection abilities://// 1. @This() type//// Returns the innermost struct, enum, or union that a function// call is inside.//// 2. @typeInfo(comptime T: type) @import("std").builtin.Type//// Returns information about any type in a data structure which// will contain different information depending on which type// you're examining.//// 3. @TypeOf(...) type//// Returns the type common to all input parameters (each of which// may be any expression). The type is resolved using the same// "peer type resolution" process the compiler itself uses when// inferring types.//// (Notice how the two functions which return types start with// uppercase letters? This is a standard naming practice in Zig.)//const print = @import("std").debug.print;const Narcissus = struct {me: *Narcissus = undefined,myself: *Narcissus = undefined,echo: void = undefined, // Alas, poor Echo!fn fetchTheMostBeautifulType() type {return @This();}};pub fn main() void {var narcissus: Narcissus = Narcissus{};// Oops! We cannot leave the 'me' and 'myself' fields// undefined. Please set them here:narcissus.me = &narcissus;narcissus.??? = ???;// This determines a "peer type" from three separate// references (they just happen to all be the same object).const Type1 = @TypeOf(narcissus, narcissus.me.*, narcissus.myself.*);// Oh dear, we seem to have done something wrong when calling// this function. We called it as a method, which would work// if it had a self parameter. But it doesn't. (See above.)//// The fix for this is very subtle, but it makes a big// difference!const Type2 = narcissus.fetchTheMostBeautifulType();// Now we print a pithy statement about Narcissus.print("A {s} loves all {s}es. ", .{maximumNarcissism(Type1),maximumNarcissism(Type2),});// His final words as he was looking in// those waters he habitually watched// were these:// "Alas, my beloved boy, in vain!"// The place gave every word back in reply.// He cried:// "Farewell."// And Echo called:// "Farewell!"//// --Ovid, The Metamorphoses// translated by Ian Johnstonprint("He has room in his heart for:", .{});// A StructFields arrayconst fields = @typeInfo(Narcissus).@"struct".fields;// 'fields' is a slice of StructFields. Here's the declaration://// pub const StructField = struct {// name: [:0]const u8,// type: type,// default_value_ptr: ?*const anyopaque,// is_comptime: bool,// alignment: comptime_int,//// defaultValue() ?sf.type // Function that loads the// // field's default value from// // `default_value_ptr`// };//// Please complete these 'if' statements so that the field// name will not be printed if the field is of type 'void'// (which is a zero-bit type that takes up no space at all!):if (fields[0].??? != void) {print(" {s}", .{fields[0].name});}if (fields[1].??? != void) {print(" {s}", .{fields[1].name});}if (fields[2].??? != void) {print(" {s}", .{fields[2].name});}// Yuck, look at all that repeated code above! I don't know// about you, but it makes me itchy.//// Alas, we can't use a regular 'for' loop here because// 'fields' can only be evaluated at compile time. It seems// like we're overdue to learn about this "comptime" stuff,// doesn't it? Don't worry, we'll get there.print(".\n", .{});}// NOTE: This exercise did not originally include the function below.// But a change after Zig 0.10.0 added the source file name to the// type. "Narcissus" became "065_builtins2.Narcissus".//// To fix this, we've added this function to strip the filename from// the front of the type name. (It returns a slice of the type name// starting at the index + 1 of character ".")//// We'll be seeing @typeName again in Exercise 070. For now, you can// see that it takes a Type and returns a u8 "string".fn maximumNarcissism(myType: anytype) []const u8 {const indexOf = @import("std").mem.indexOf;// Turn "065_builtins2.Narcissus" into "Narcissus"const name = @typeName(myType);return name[indexOf(u8, name, ".").? + 1 ..];}
//// The Zig compiler provides "builtin" functions. You've already// gotten used to seeing an @import() at the top of every// Ziglings exercise.//// We've also seen @intCast() in "016_for2.zig", "058_quiz7.zig";// and @intFromEnum() in "036_enums2.zig".//// Builtins are special because they are intrinsic to the Zig// language itself (as opposed to being provided in the standard// library). They are also special because they can provide// functionality that is only possible with help from the// compiler, such as type introspection (the ability to examine// type properties from within a program).//// Zig contains over 100 builtin functions. We're certainly// not going to cover them all, but we can look at some// interesting ones.//// Before we begin, know that many builtin functions have// parameters marked as "comptime". It's probably fairly clear// what we mean when we say that these parameters need to be// "known at compile time." But rest assured we'll be doing the// "comptime" subject real justice soon.//const print = @import("std").debug.print;pub fn main() void {// The second builtin, alphabetically, is:// @addWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }// * 'a' and 'b' are numbers of anytype.// * The return value is a tuple with the result and a possible overflow bit.//// Let's try it with a tiny 4-bit integer size to make it clear:const a: u4 = 0b1101;const b: u4 = 0b0101;const my_result = @addWithOverflow(a, b);// Check out our fancy formatting! b:0>4 means, "print// as a binary number, zero-pad right-aligned four digits."// The print() below will produce: "1101 + 0101 = 0010 (true)".print("{b:0>4} + {b:0>4} = {b:0>4} ({s})", .{ a, b, my_result[0], if (my_result[1] == 1) "true" else "false" });// Let's make sense of this answer. The value of 'b' in decimal is 5.// Let's add 5 to 'a' but go one by one and see where it overflows://// a | b | result | overflowed?// ----------------------------------// 1101 + 0001 = 1110 | false// 1110 + 0001 = 1111 | false// 1111 + 0001 = 0000 | true (the real answer is 10000)// 0000 + 0001 = 0001 | false// 0001 + 0001 = 0010 | false//// In the last two lines the value of 'a' is corrupted because there was// an overflow in line 3, but the operations of lines 4 and 5 themselves// do not overflow.// There is a difference between// - a value, that overflowed at some point and is now corrupted// - a single operation that overflows and maybe causes subsequent errors// In practice we usually notice the overflowed value first and have to work// our way backwards to the operation that caused the overflow.//// If there was no overflow at all while adding 5 to a, what value would// 'my_result' hold? Write the answer in into 'expected_result'.const expected_result: u8 = ???;print(". Without overflow: {b:0>8}. ", .{expected_result});print("Furthermore, ", .{});// Here's a fun one://// @bitReverse(integer: anytype) T// * 'integer' is the value to reverse.// * The return value will be the same type with the// value's bits reversed!//// Now it's your turn. See if you can fix this attempt to use// this builtin to reverse the bits of a u8 integer.const input: u8 = 0b11110000;const tupni: u8 = @bitReverse(input, tupni);print("{b:0>8} backwards is {b:0>8}.\n", .{ input, tupni });}
//// Loop bodies are blocks, which are also expressions. We've seen// how they can be used to evaluate and return values. To further// expand on this concept, it turns out we can also give names to// blocks by applying a 'label'://// my_label: { ... }//// Once you give a block a label, you can use 'break' to exit// from that block.//// outer_block: { // outer block// while (true) { // inner block// break :outer_block;// }// unreachable;// }//// As we've just learned, you can return a value using a break// statement. Does that mean you can return a value from any// labeled block? Yes it does!//// const foo = make_five: {// const five = 1 + 1 + 1 + 1 + 1;// break :make_five five;// };//// Labels can also be used with loops. Being able to break out of// nested loops at a specific level is one of those things that// you won't use every day, but when the time comes, it's// incredibly convenient. Being able to return a value from an// inner loop is sometimes so handy, it almost feels like cheating// (and can help you avoid creating a lot of temporary variables).//// const bar: u8 = two_loop: while (true) {// while (true) {// break :two_loop 2;// }// } else 0;//// In the above example, the break exits from the outer loop// labeled "two_loop" and returns the value 2. The else clause is// attached to the outer two_loop and would be evaluated if the// loop somehow ended without the break having been called.//// Finally, you can also use block labels with the 'continue'// statement://// my_while: while (true) {// continue :my_while;// }//const print = @import("std").debug.print;// As mentioned before, we'll soon understand why these two// numbers don't need explicit types. Hang in there!const ingredients = 4;const foods = 4;const Food = struct {name: []const u8,requires: [ingredients]bool,};// Chili Macaroni Tomato Sauce Cheese// ------------------------------------------------------// Mac & Cheese x x// Chili Mac x x// Pasta x x// Cheesy Chili x x// ------------------------------------------------------const menu: [foods]Food = [_]Food{Food{.name = "Mac & Cheese",.requires = [ingredients]bool{ false, true, false, true },},Food{.name = "Chili Mac",.requires = [ingredients]bool{ true, true, false, false },},Food{.name = "Pasta",.requires = [ingredients]bool{ false, true, true, false },},Food{.name = "Cheesy Chili",.requires = [ingredients]bool{ true, false, false, true },},};pub fn main() void {// Welcome to Cafeteria USA! Choose your favorite ingredients// and we'll produce a delicious meal.//// Cafeteria Customer Note: Not all ingredient combinations// make a meal. The default meal is macaroni and cheese.//// Software Developer Note: Hard-coding the ingredient// numbers (based on array position) will be fine for our// tiny example, but it would be downright criminal in a real// application!const wanted_ingredients = [_]u8{ 0, 3 }; // Chili, Cheese// Look at each Food on the menu...const meal = food_loop: for (menu) |food| {// Now look at each required ingredient for the Food...for (food.requires, 0..) |required, required_ingredient| {// This ingredient isn't required, so skip it.if (!required) continue;// See if the customer wanted this ingredient.// (Remember that want_it will be the index number of// the ingredient based on its position in the// required ingredient list for each food.)const found = for (wanted_ingredients) |want_it| {if (required_ingredient == want_it) break true;} else false;// We did not find this required ingredient, so we// can't make this Food. Continue the outer loop.if (!found) continue :food_loop;}// If we get this far, the required ingredients were all// wanted for this Food.//// Please return this Food from the loop.break;};// ^ Oops! We forgot to return Mac & Cheese as the default// Food when the requested ingredients aren't found.print("Enjoy your {s}!\n", .{meal.name});}// Challenge: You can also do away with the 'found' variable in// the inner loop. See if you can figure out how to do that!
//// Remember using if/else statements as expressions like this?//// var foo: u8 = if (true) 5 else 0;//// Zig also lets you use for and while loops as expressions.//// Like 'return' for functions, you can return a value from a// loop block with break://// break true; // return boolean value from block//// But what value is returned from a loop if a break statement is// never reached? We need a default expression. Thankfully, Zig// loops also have 'else' clauses! As you might have guessed, the// 'else' clause is evaluated when: 1) a 'while' condition becomes// false or 2) a 'for' loop runs out of items.//// const two: u8 = while (true) break 2 else 0; // 2// const three: u8 = for ([1]u8{1}) |f| break 3 else 0; // 3//// If you do not provide an else clause, an empty one will be// provided for you, which will evaluate to the void type, which// is probably not what you want. So consider the else clause// essential when using loops as expressions.//// const four: u8 = while (true) {// break 4;// }; // <-- ERROR! Implicit 'else void' here!//// With that in mind, see if you can fix the problem with this// program.//const print = @import("std").debug.print;pub fn main() void {const langs: [6][]const u8 = .{"Erlang","Algol","C","OCaml","Zig","Prolog",};// Let's find the first language with a three-letter name and// return it from the for loop.const current_lang: ?[]const u8 = for (langs) |lang| {if (lang.len == 3) break lang;};if (current_lang) |cl| {print("Current language: {s}\n", .{cl});} else {print("Did not find a three-letter language name. :-(\n", .{});}}
//// It'll only take us a moment to learn the Zig type coercion// rules because they're quite logical.//// 1. Types can always be made _more_ restrictive.//// var foo: u8 = 5;// var p1: *u8 = &foo;// var p2: *const u8 = p1; // mutable to immutable//// 2. Numeric types can coerce to _larger_ types.//// var n1: u8 = 5;// var n2: u16 = n1; // integer "widening"//// var n3: f16 = 42.0;// var n4: f32 = n3; // float "widening"//// 3. Single-item pointers to arrays coerce to slices and// many-item pointers.//// const arr: [3]u8 = [3]u8{5, 6, 7};// const s: []const u8 = &arr; // to slice// const p: [*]const u8 = &arr; // to many-item pointer//// 4. Single-item mutable pointers can coerce to single-item// pointers pointing to an array of length 1. (Interesting!)//// var five: u8 = 5;// var a_five: *[1]u8 = &five;//// 5. Payload types and null coerce to optionals.//// var num: u8 = 5;// var maybe_num: ?u8 = num; // payload type// maybe_num = null; // null//// 6. Payload types and errors coerce to error unions.//// const MyError = error{Argh};// var char: u8 = 'x';// var char_or_die: MyError!u8 = char; // payload type// char_or_die = MyError.Argh; // error//// 7. 'undefined' coerces to any type (or it wouldn't work!)//// 8. Compile-time numbers coerce to compatible types.//// Just about every single exercise program has had an example// of this, but a full and proper explanation is coming your// way soon in the third-eye-opening subject of comptime.//// 9. Tagged unions coerce to the current tagged enum.//// 10. Enums coerce to a tagged union when that tagged field is a// zero-length type that has only one value (like void).//// 11. Zero-bit types (like void) can be coerced into single-item// pointers.//// The last three are fairly esoteric, but you're more than// welcome to read more about them in the official Zig language// documentation and write your own experiments.const print = @import("std").debug.print;pub fn main() void {var letter: u8 = 'A';const my_letter: ??? = &letter;// ^^^^^^^// Your type here.// Must coerce from &letter (which is a *u8).// Hint: Use coercion Rules 4 and 5.// When it's right, this will work:print("Letter: {u}\n", .{my_letter.?.*[0]});}
//// Zig has support for IEEE-754 floating-point numbers in these// specific sizes: f16, f32, f64, f80, and f128. Floating point// literals may be written in the same ways as integers but also// in scientific notation://// const a1: f32 = 1200; // 1,200// const a2: f32 = 1.2e+3; // 1,200// const b1: f32 = -500_000.0; // -500,000// const b2: f32 = -5.0e+5; // -500,000//// Hex floats can't use the letter 'e' because that's a hex// digit, so we use a 'p' instead://// const hex: f16 = 0x2A.F7p+3; // Wow, that's arcane!//// Be sure to use a float type that is large enough to store your// value (both in terms of significant digits and scale).// Rounding may or may not be okay, but numbers which are too// large or too small become inf or -inf (positive or negative// infinity)!//// const pi: f16 = 3.1415926535; // rounds to 3.140625// const av: f16 = 6.02214076e+23; // Avogadro's inf(inity)!//// When performing math operations with numeric literals, ensure// the types match. Zig does not perform unsafe type coercions// behind your back://// var foo: f16 = 5; // NO ERROR//// var foo: u16 = 5; // A literal of a different type// var bar: f16 = foo; // ERROR//// Please fix the two float problems with this program and// display the result as a whole number.const print = @import("std").debug.print;pub fn main() void {// The approximate weight of the Space Shuttle upon liftoff// (including boosters and fuel tank) was 4,480,000 lb.//// We'll convert this weight from pounds to metric units at a// conversion of 0.453592 kg to the pound.const shuttle_weight: f16 = 0.453592 * 4480e3;// By default, float values are formatted in scientific// notation. Try experimenting with '{d}' and '{d:.3}' to see// how decimal formatting works.print("Shuttle liftoff weight: {d:.0} metric tons\n", .{shuttle_weight});}// Floating further://// As an example, Zig's f16 is a IEEE 754 "half-precision" binary// floating-point format ("binary16"), which is stored in memory// like so://// 0 1 0 0 0 0 1 0 0 1 0 0 1 0 0 0// | |-------| |-----------------|// | exponent significand// |// sign//// This example is the decimal number 3.140625, which happens to// be the closest representation of Pi we can make with an f16// due to the way IEEE-754 floating points store digits://// * Sign bit 0 makes the number positive.// * Exponent bits 10000 are a scale of 16.// * Significand bits 1001001000 are the decimal value 584.//// IEEE-754 saves space by modifying these values: the value// 01111 is always subtracted from the exponent bits (in our// case, 10000 - 01111 = 1, so our exponent is 2^1) and our// significand digits become the decimal value _after_ an// implicit 1 (so 1.1001001000 or 1.5703125 in decimal)! This// gives us://// 2^1 * 1.5703125 = 3.140625//// Feel free to forget these implementation details immediately.// The important thing to know is that floating point numbers are// great at storing big and small values (f64 lets you work with// numbers on the scale of the number of atoms in the universe),// but digits may be rounded, leading to results which are less// precise than integers.//// Fun fact: sometimes you'll see the significand labeled as a// "mantissa" but Donald E. Knuth says not to do that.//// C compatibility fact: There is also a Zig floating point type// specifically for working with C ABIs called c_longdouble.
//// Zig lets you express integer literals in several convenient// formats. These are all the same value://// const a1: u8 = 65; // decimal// const a2: u8 = 0x41; // hexadecimal// const a3: u8 = 0o101; // octal// const a4: u8 = 0b1000001; // binary// const a5: u8 = 'A'; // ASCII code point literal// const a6: u16 = '\u{0041}'; // Unicode code points can take up to 21 bits//// You can also place underscores in numbers to aid readability://// const t1: u32 = 14_689_520 // Ford Model T sales 1909-1927// const t2: u32 = 0xE0_24_F0 // same, in hex pairs//// Please fix the message:const print = @import("std").debug.print;pub fn main() void {const zig = [_]u8{0o131, // octal0b1101000, // binary0x66, // hex};print("{s} is cool.\n", .{zig});}
//// We've absorbed a lot of information about the variations of types// we can use in Zig. Roughly, in order we have://// u8 single item// *u8 single-item pointer// []u8 slice (size known at runtime)// [5]u8 array of 5 u8s// [*]u8 many-item pointer (zero or more)// enum {a, b} set of unique values a and b// error {e, f} set of unique error values e and f// struct {y: u8, z: i32} group of values y and z// union(enum) {a: u8, b: i32} single value either u8 or i32//// Values of any of the above types can be assigned as "var" or "const"// to allow or disallow changes (mutability) via the assigned name://// const a: u8 = 5; // immutable// var b: u8 = 5; // mutable//// We can also make error unions or optional types from any of// the above://// var a: E!u8 = 5; // can be u8 or error from set E// var b: ?u8 = 5; // can be u8 or null//// Knowing all of this, maybe we can help out a local hermit. He made// a little Zig program to help him plan his trips through the woods,// but it has some mistakes.//// *************************************************************// * A NOTE ABOUT THIS EXERCISE *// * *// * You do NOT have to read and understand every bit of this *// * program. This is a very big example. Feel free to skim *// * through it and then just focus on the few parts that are *// * actually broken! *// * *// *************************************************************//const print = @import("std").debug.print;// The grue is a nod to Zork.const TripError = error{ Unreachable, EatenByAGrue };// Let's start with the Places on the map. Each has a name and a// distance or difficulty of travel (as judged by the hermit).//// Note that we declare the places as mutable (var) because we need to// assign the paths later. And why is that? Because paths contain// pointers to places and assigning them now would create a dependency// loop!const Place = struct {name: []const u8,paths: []const Path = undefined,};var a = Place{ .name = "Archer's Point" };var b = Place{ .name = "Bridge" };var c = Place{ .name = "Cottage" };var d = Place{ .name = "Dogwood Grove" };var e = Place{ .name = "East Pond" };var f = Place{ .name = "Fox Pond" };// The hermit's hand-drawn ASCII map// +---------------------------------------------------+// | * Archer's Point ~~~~ |// | ~~~ ~~~~~~~~ |// | ~~~| |~~~~~~~~~~~~ ~~~~~~~ |// | Bridge ~~~~~~~~ |// | ^ ^ ^ |// | ^ ^ / \ |// | ^ ^ ^ ^ |_| Cottage |// | Dogwood Grove |// | ^ <boat> |// | ^ ^ ^ ^ ~~~~~~~~~~~~~ ^ ^ |// | ^ ~~ East Pond ~~~ |// | ^ ^ ^ ~~~~~~~~~~~~~~ |// | ~~ ^ |// | ^ ~~~ <-- short waterfall |// | ^ ~~~~~ |// | ~~~~~~~~~~~~~~~~~ |// | ~~~~ Fox Pond ~~~~~~~ ^ ^ |// | ^ ~~~~~~~~~~~~~~~ ^ ^ |// | ~~~~~ |// +---------------------------------------------------+//// We'll be reserving memory in our program based on the number of// places on the map. Note that we do not have to specify the type of// this value because we don't actually use it in our program once// it's compiled! (Don't worry if this doesn't make sense yet.)const place_count = 6;// Now let's create all of the paths between sites. A path goes from// one place to another and has a distance.const Path = struct {from: *const Place,to: *const Place,dist: u8,};// By the way, if the following code seems like a lot of tedious// manual labor, you're right! One of Zig's killer features is letting// us write code that runs at compile time to "automate" repetitive// code (much like macros in other languages), but we haven't learned// how to do that yet!const a_paths = [_]Path{Path{.from = &a, // from: Archer's Point.to = &b, // to: Bridge.dist = 2,},};const b_paths = [_]Path{Path{.from = &b, // from: Bridge.to = &a, // to: Archer's Point.dist = 2,},Path{.from = &b, // from: Bridge.to = &d, // to: Dogwood Grove.dist = 1,},};const c_paths = [_]Path{Path{.from = &c, // from: Cottage.to = &d, // to: Dogwood Grove.dist = 3,},Path{.from = &c, // from: Cottage.to = &e, // to: East Pond.dist = 2,},};const d_paths = [_]Path{Path{.from = &d, // from: Dogwood Grove.to = &b, // to: Bridge.dist = 1,},Path{.from = &d, // from: Dogwood Grove.to = &c, // to: Cottage.dist = 3,},Path{.from = &d, // from: Dogwood Grove.to = &f, // to: Fox Pond.dist = 7,},};const e_paths = [_]Path{Path{.from = &e, // from: East Pond.to = &c, // to: Cottage.dist = 2,},Path{.from = &e, // from: East Pond.to = &f, // to: Fox Pond.dist = 1, // (one-way down a short waterfall!)},};const f_paths = [_]Path{Path{.from = &f, // from: Fox Pond.to = &d, // to: Dogwood Grove.dist = 7,},};// Once we've plotted the best course through the woods, we'll make a// "trip" out of it. A trip is a series of Places connected by Paths.// We use a TripItem union to allow both Places and Paths to be in the// same array.const TripItem = union(enum) {place: *const Place,path: *const Path,// This is a little helper function to print the two different// types of item correctly.fn printMe(self: TripItem) void {switch (self) {// Oops! The hermit forgot how to capture the union values// in a switch statement. Please capture each value as// 'p' so the print statements work!.place => print("{s}", .{p.name}),.path => print("--{}->", .{p.dist}),}}};// The Hermit's Notebook is where all the magic happens. A notebook// entry is a Place discovered on the map along with the Path taken to// get there and the distance to reach it from the start point. If we// find a better Path to reach a Place (shorter distance), we update the// entry. Entries also serve as a "todo" list which is how we keep// track of which paths to explore next.const NotebookEntry = struct {place: *const Place,coming_from: ?*const Place,via_path: ?*const Path,dist_to_reach: u16,};// +------------------------------------------------+// | ~ Hermit's Notebook ~ |// +---+----------------+----------------+----------+// | | Place | From | Distance |// +---+----------------+----------------+----------+// | 0 | Archer's Point | null | 0 |// | 1 | Bridge | Archer's Point | 2 | < next_entry// | 2 | Dogwood Grove | Bridge | 1 |// | 3 | | | | < end_of_entries// | ... |// +---+----------------+----------------+----------+//const HermitsNotebook = struct {// Remember the array repetition operator `**`? It is no mere// novelty, it's also a great way to assign multiple items in an// array without having to list them one by one. Here we use it to// initialize an array with null values.entries: [place_count]?NotebookEntry = .{null} ** place_count,// The next entry keeps track of where we are in our "todo" list.next_entry: u8 = 0,// Mark the start of empty space in the notebook.end_of_entries: u8 = 0,// We'll often want to find an entry by Place. If one is not// found, we return null.fn getEntry(self: *HermitsNotebook, place: *const Place) ?*NotebookEntry {for (&self.entries, 0..) |*entry, i| {if (i >= self.end_of_entries) break;// Here's where the hermit got stuck. We need to return// an optional pointer to a NotebookEntry.//// What we have with "entry" is the opposite: a pointer to// an optional NotebookEntry!//// To get one from the other, we need to dereference// "entry" (with .*) and get the non-null value from the// optional (with .?) and return the address of that. The// if statement provides some clues about how the// dereference and optional value "unwrapping" look// together. Remember that you return the address with the// "&" operator.if (place == entry.*.?.place) return entry;// Try to make your answer this long:__________;}return null;}// The checkNote() method is the beating heart of the magical// notebook. Given a new note in the form of a NotebookEntry// struct, we check to see if we already have an entry for the// note's Place.//// If we DON'T, we'll add the entry to the end of the notebook// along with the Path taken and distance.//// If we DO, we check to see if the path is "better" (shorter// distance) than the one we'd noted before. If it is, we// overwrite the old entry with the new one.fn checkNote(self: *HermitsNotebook, note: NotebookEntry) void {const existing_entry = self.getEntry(note.place);if (existing_entry == null) {self.entries[self.end_of_entries] = note;self.end_of_entries += 1;} else if (note.dist_to_reach < existing_entry.?.dist_to_reach) {existing_entry.?.* = note;}}// The next two methods allow us to use the notebook as a "todo"// list.fn hasNextEntry(self: *HermitsNotebook) bool {return self.next_entry < self.end_of_entries;}fn getNextEntry(self: *HermitsNotebook) *const NotebookEntry {defer self.next_entry += 1; // Increment after getting entryreturn &self.entries[self.next_entry].?;}// After we've completed our search of the map, we'll have// computed the shortest Path to every Place. To collect the// complete trip from the start to the destination, we need to// walk backwards from the destination's notebook entry, following// the coming_from pointers back to the start. What we end up with// is an array of TripItems with our trip in reverse order.//// We need to take the trip array as a parameter because we want// the main() function to "own" the array memory. What do you// suppose could happen if we allocated the array in this// function's stack frame (the space allocated for a function's// "local" data) and returned a pointer or slice to it?//// Looks like the hermit forgot something in the return value of// this function. What could that be?fn getTripTo(self: *HermitsNotebook, trip: []?TripItem, dest: *Place) void {// We start at the destination entry.const destination_entry = self.getEntry(dest);// This function needs to return an error if the requested// destination was never reached. (This can't actually happen// in our map since every Place is reachable by every other// Place.)if (destination_entry == null) {return TripError.Unreachable;}// Variables hold the entry we're currently examining and an// index to keep track of where we're appending trip items.var current_entry = destination_entry.?;var i: u8 = 0;// At the end of each looping, a continue expression increments// our index. Can you see why we need to increment by two?while (true) : (i += 2) {trip[i] = TripItem{ .place = current_entry.place };// An entry "coming from" nowhere means we've reached the// start, so we're done.if (current_entry.coming_from == null) break;// Otherwise, entries have a path.trip[i + 1] = TripItem{ .path = current_entry.via_path.? };// Now we follow the entry we're "coming from". If we// aren't able to find the entry we're "coming from" by// Place, something has gone horribly wrong with our// program! (This really shouldn't ever happen. Have you// checked for grues?)// Note: you do not need to fix anything here.const previous_entry = self.getEntry(current_entry.coming_from.?);if (previous_entry == null) return TripError.EatenByAGrue;current_entry = previous_entry.?;}}};pub fn main() void {// Here's where the hermit decides where he would like to go. Once// you get the program working, try some different Places on the// map!const start = &a; // Archer's Pointconst destination = &f; // Fox Pond// Store each Path array as a slice in each Place. As mentioned// above, we needed to delay making these references to avoid// creating a dependency loop when the compiler is trying to// figure out how to allocate space for each item.a.paths = a_paths[0..];b.paths = b_paths[0..];c.paths = c_paths[0..];d.paths = d_paths[0..];e.paths = e_paths[0..];f.paths = f_paths[0..];// Now we create an instance of the notebook and add the first// "start" entry. Note the null values. Read the comments for the// checkNote() method above to see how this entry gets added to// the notebook.var notebook = HermitsNotebook{};var working_note = NotebookEntry{.place = start,.coming_from = null,.via_path = null,.dist_to_reach = 0,};notebook.checkNote(working_note);// Get the next entry from the notebook (the first being the// "start" entry we just added) until we run out, at which point// we'll have checked every reachable Place.while (notebook.hasNextEntry()) {const place_entry = notebook.getNextEntry();// For every Path that leads FROM the current Place, create a// new note (in the form of a NotebookEntry) with the// destination Place and the total distance from the start to// reach that place. Again, read the comments for the// checkNote() method to see how this works.for (place_entry.place.paths) |*path| {working_note = NotebookEntry{.place = path.to,.coming_from = place_entry.place,.via_path = path,.dist_to_reach = place_entry.dist_to_reach + path.dist,};notebook.checkNote(working_note);}}// Once the loop above is complete, we've calculated the shortest// path to every reachable Place! What we need to do now is set// aside memory for the trip and have the hermit's notebook fill// in the trip from the destination back to the path. Note that// this is the first time we've actually used the destination!var trip = [_]?TripItem{null} ** (place_count * 2);notebook.getTripTo(trip[0..], destination) catch |err| {print("Oh no! {}\n", .{err});return;};// Print the trip with a little helper function below.printTrip(trip[0..]);}// Remember that trips will be a series of alternating TripItems// containing a Place or Path from the destination back to the start.// The remaining space in the trip array will contain null values, so// we need to loop through the items in reverse, skipping nulls, until// we reach the destination at the front of the array.fn printTrip(trip: []?TripItem) void {// We convert the usize length to a u8 with @intCast(), a// builtin function just like @import(). We'll learn about// these properly in a later exercise.var i: u8 = @intCast(trip.len);while (i > 0) {i -= 1;if (trip[i] == null) continue;trip[i].?.printMe();}print("\n", .{});}// Going deeper://// In computer science terms, our map places are "nodes" or "vertices" and// the paths are "edges". Together, they form a "weighted, directed// graph". It is "weighted" because each path has a distance (also// known as a "cost"). It is "directed" because each path goes FROM// one place TO another place (undirected graphs allow you to travel// on an edge in either direction).//// Since we append new notebook entries at the end of the list and// then explore each sequentially from the beginning (like a "todo"// list), we are treating the notebook as a "First In, First Out"// (FIFO) queue.//// Since we examine all closest paths first before trying further ones// (thanks to the "todo" queue), we are performing a "Breadth-First// Search" (BFS).//// By tracking "lowest cost" paths, we can also say that we're// performing a "least-cost search".//// Even more specifically, the Hermit's Notebook most closely// resembles the Shortest Path Faster Algorithm (SPFA), attributed to// Edward F. Moore. By replacing our simple FIFO queue with a// "priority queue", we would basically have Dijkstra's algorithm. A// priority queue retrieves items sorted by "weight" (in our case, it// would keep the paths with the shortest distance at the front of the// queue). Dijkstra's algorithm is more efficient because longer paths// can be eliminated more quickly. (Work it out on paper to see why!)
//// With tagged unions, it gets EVEN BETTER! If you don't have a// need for a separate enum, you can define an inferred enum with// your union all in one place. Just use the 'enum' keyword in// place of the tag type://// const Foo = union(enum) {// small: u8,// medium: u32,// large: u64,// };//// Let's convert Insect. Doctor Zoraptera has already deleted the// explicit InsectStat enum for you!//const std = @import("std");const Insect = union(InsectStat) {flowers_visited: u16,still_alive: bool,};pub fn main() void {const ant = Insect{ .still_alive = true };const bee = Insect{ .flowers_visited = 17 };std.debug.print("Insect report! ", .{});printInsect(ant);printInsect(bee);std.debug.print("\n", .{});}fn printInsect(insect: Insect) void {switch (insect) {.still_alive => |a| std.debug.print("Ant alive is: {}. ", .{a}),.flowers_visited => |f| std.debug.print("Bee visited {} flowers. ", .{f}),}}// Inferred enums are neat, representing the tip of the iceberg// in the relationship between enums and unions. You can actually// coerce a union TO an enum (which gives you the active field// from the union as an enum). What's even wilder is that you can// coerce an enum to a union! But don't get too excited, that// only works when the union type is one of those weird zero-bit// types like void!//// Tagged unions, as with most ideas in computer science, have a// long history going back to the 1960s. However, they're only// recently becoming mainstream, particularly in system-level// programming languages. You might have also seen them called// "variants", "sum types", or even "enums"!
//// It is really quite inconvenient having to manually keep track// of the active field in our union, isn't it?//// Thankfully, Zig also has "tagged unions", which allow us to// store an enum value within our union representing which field// is active.//// const FooTag = enum{ small, medium, large };//// const Foo = union(FooTag) {// small: u8,// medium: u32,// large: u64,// };//// Now we can use a switch directly on the union to act on the// active field://// var f = Foo{ .small = 10 };//// switch (f) {// .small => |my_small| do_something(my_small),// .medium => |my_medium| do_something(my_medium),// .large => |my_large| do_something(my_large),// }//// Let's make our Insects use a tagged union (Doctor Zoraptera// approves).//const std = @import("std");const InsectStat = enum { flowers_visited, still_alive };const Insect = union(InsectStat) {flowers_visited: u16,still_alive: bool,};pub fn main() void {const ant = Insect{ .still_alive = true };const bee = Insect{ .flowers_visited = 16 };std.debug.print("Insect report! ", .{});// Could it really be as simple as just passing the union?printInsect(???);printInsect(???);std.debug.print("\n", .{});}fn printInsect(insect: Insect) void {switch (???) {.still_alive => |a| std.debug.print("Ant alive is: {}. ", .{a}),.flowers_visited => |f| std.debug.print("Bee visited {} flowers. ", .{f}),}}// By the way, did unions remind you of optional values and errors?// Optional values are basically "null unions" and errors use "error// union types". Now we can add our own unions to the mix to handle// whatever situations we might encounter:// union(Tag) { value: u32, toxic_ooze: void }
//// A union lets you store different types and sizes of data at// the same memory address. How is this possible? The compiler// sets aside enough memory for the largest thing you might want// to store.//// In this example, an instance of Foo always takes up u64 of// space in memory even if you're currently storing a u8.//// const Foo = union {// small: u8,// medium: u32,// large: u64,// };//// The syntax looks just like a struct, but a Foo can only hold a// small OR a medium OR a large value. Once a field becomes// active, the other inactive fields cannot be accessed. To// change active fields, assign a whole new instance://// var f = Foo{ .small = 5 };// f.small += 5; // OKAY// f.medium = 5432; // ERROR!// f = Foo{ .medium = 5432 }; // OKAY//// Unions can save space in memory because they let you "re-use"// a space in memory. They also provide a sort of primitive// polymorphism. Here fooBar() can take a Foo no matter what size// of unsigned integer it holds://// fn fooBar(f: Foo) void { ... }//// Oh, but how does fooBar() know which field is active? Zig has// a neat way of keeping track, but for now, we'll just have to// do it manually.//// Let's see if we can get this program working!//const std = @import("std");// We've just started writing a simple ecosystem simulation.// Insects will be represented by either bees or ants. Bees store// the number of flowers they've visited that day and ants just// store whether or not they're still alive.const Insect = union {flowers_visited: u16,still_alive: bool,};// Since we need to specify the type of insect, we'll use an// enum (remember those?).const AntOrBee = enum { a, b };pub fn main() void {// We'll just make one bee and one ant to test them out:const ant = Insect{ .still_alive = true };const bee = Insect{ .flowers_visited = 15 };std.debug.print("Insect report! ", .{});// Oops! We've made a mistake here.printInsect(ant, AntOrBee.c);printInsect(bee, AntOrBee.c);std.debug.print("\n", .{});}// Eccentric Doctor Zoraptera says that we can only use one// function to print our insects. Doctor Z is small and sometimes// inscrutable but we do not question her.fn printInsect(insect: Insect, what_it_is: AntOrBee) void {switch (what_it_is) {.a => std.debug.print("Ant alive is: {}. ", .{insect.still_alive}),.b => std.debug.print("Bee visited {} flowers. ", .{insect.flowers_visited}),}}
//// You can also make pointers to multiple items without using a slice.//// var foo: [4]u8 = [4]u8{ 1, 2, 3, 4 };// var foo_slice: []u8 = foo[0..];// var foo_ptr: [*]u8 = &foo;// var foo_slice_from_ptr: []u8 = foo_ptr[0..4];//// The difference between foo_slice and foo_ptr is that the slice has// a known length. The pointer doesn't. It is up to YOU to keep track// of the number of u8s foo_ptr points to!//const std = @import("std");pub fn main() void {// Take a good look at the array type to which we're coercing// the zen12 string (the REAL nature of strings will be// revealed when we've learned some additional features):const zen12: *const [21]u8 = "Memory is a resource.";//// It would also have been valid to coerce to a slice:// const zen12: []const u8 = "...";//// Now let's turn this into a "many-item pointer":const zen_manyptr: [*]const u8 = zen12;// It's okay to access zen_manyptr just like an array or slice as// long as you keep track of the length yourself!//// A "string" in Zig is a pointer to an array of const u8 values// (or a slice of const u8 values, as we saw above). So, we could// treat a "many-item pointer" of const u8 as a string as long as// we can CONVERT IT TO A SLICE. (Hint: we do know the length!)//// Please fix this line so the print statement below can print it:const zen12_string: []const u8 = zen_manyptr;// Here's the moment of truth!std.debug.print("{s}\n", .{zen12_string});}//// Are all of these pointer types starting to get confusing?//// FREE ZIG POINTER CHEATSHEET! (Using u8 as the example type.)// +---------------+----------------------------------------------+// | u8 | one u8 |// | *u8 | pointer to one u8 |// | [2]u8 | two u8s |// | [*]u8 | pointer to unknown number of u8s |// | [*]const u8 | pointer to unknown number of immutable u8s |// | *[2]u8 | pointer to an array of 2 u8s |// | *const [2]u8 | pointer to an immutable array of 2 u8s |// | []u8 | slice of u8s |// | []const u8 | slice of immutable u8s |// +---------------+----------------------------------------------+
//// You are perhaps tempted to try slices on strings? They're arrays of// u8 characters after all, right? Slices on strings work great.// There's just one catch: don't forget that Zig string literals are// immutable (const) values. So we need to change the type of slice// from://// var foo: []u8 = "foobar"[0..3];//// to://// var foo: []const u8 = "foobar"[0..3];//// See if you can fix this Zero Wing-inspired phrase descrambler:const std = @import("std");pub fn main() void {const scrambled = "great base for all your justice are belong to us";const base1: []u8 = scrambled[15..23];const base2: []u8 = scrambled[6..10];const base3: []u8 = scrambled[32..];printPhrase(base1, base2, base3);const justice1: []u8 = scrambled[11..14];const justice2: []u8 = scrambled[0..5];const justice3: []u8 = scrambled[24..31];printPhrase(justice1, justice2, justice3);std.debug.print("\n", .{});}fn printPhrase(part1: []u8, part2: []u8, part3: []u8) void {std.debug.print("'{s} {s} {s}.' ", .{ part1, part2, part3 });}
//// We've seen that passing arrays around can be awkward. Perhaps you// remember a particularly horrendous function definition from quiz3?// This function can only take arrays that are exactly 4 items long!//// fn printPowersOfTwo(numbers: [4]u16) void { ... }//// That's the trouble with arrays - their size is part of the data// type and must be hard-coded into every usage of that type. This// digits array is a [10]u8 forever and ever://// var digits = [10]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };//// Thankfully, Zig has slices, which let you dynamically point to a// start item and provide a length. Here are slices of our digit// array://// const foo = digits[0..1]; // 0// const bar = digits[3..9]; // 3 4 5 6 7 8// const baz = digits[5..9]; // 5 6 7 8// const all = digits[0..]; // 0 1 2 3 4 5 6 7 8 9//// As you can see, a slice [x..y] starts with the index of the// first item at x and the last item at y-1. You can leave the y// off to get "the rest of the items".//// The type of a slice on an array of u8 items is []u8.//const std = @import("std");pub fn main() void {var cards = [8]u8{ 'A', '4', 'K', '8', '5', '2', 'Q', 'J' };// Please put the first 4 cards in hand1 and the rest in hand2.const hand1: []u8 = cards[???];const hand2: []u8 = cards[???];std.debug.print("Hand1: ", .{});printHand(hand1);std.debug.print("Hand2: ", .{});printHand(hand2);}// Please lend this function a hand. A u8 slice hand, that is.fn printHand(hand: ???) void {for (hand) |h| {std.debug.print("{u} ", .{h});}std.debug.print("\n", .{});}//// Fun fact: Under the hood, slices are stored as a pointer to// the first item and a length.
//// If you thought the last exercise was a deep dive, hold onto your// hat because we are about to descend into the computer's molten// core.//// (Shouting) DOWN HERE, THE BITS AND BYTES FLOW FROM RAM TO THE CPU// LIKE A HOT, DENSE FLUID. THE FORCES ARE INCREDIBLE. BUT HOW DOES// ALL OF THIS RELATE TO THE DATA IN OUR ZIG PROGRAMS? LET'S HEAD// BACK UP TO THE TEXT EDITOR AND FIND OUT.//// Ah, that's better. Now we can look at some familiar Zig code.//// @import() adds the imported code to your own. In this case, code// from the standard library is added to your program and compiled// with it. All of this will be loaded into RAM when it runs. Oh, and// that thing we name "const std"? That's a struct!//const std = @import("std");// Remember our old RPG Character struct? A struct is really just a// very convenient way to deal with memory. These fields (gold,// health, experience) are all values of a particular size. Add them// together and you have the size of the struct as a whole.const Character = struct {gold: u32 = 0,health: u8 = 100,experience: u32 = 0,};// Here we create a character called "the_narrator" that is a constant// (immutable) instance of a Character struct. It is stored in your// program as data, and like the instruction code, it is loaded into// RAM when your program runs. The relative location of this data in// memory is hard-coded and neither the address nor the value changes.const the_narrator = Character{.gold = 12,.health = 99,.experience = 9000,};// This "global_wizard" character is very similar. The address for// this data won't change, but the data itself can since this is a var// and not a const.var global_wizard = Character{};// A function is instruction code at a particular address. Function// parameters in Zig are always immutable. They are stored in "the// stack". A stack is a type of data structure and "the stack" is a// specific bit of RAM reserved for your program. The CPU has special// support for adding and removing things from "the stack", so it is// an extremely efficient place for memory storage.//// Also, when a function executes, the input arguments are often// loaded into the beating heart of the CPU itself in registers.//// Our main() function here has no input parameters, but it will have// a stack entry (called a "frame").pub fn main() void {// Here, the "glorp" character will be allocated on the stack// because each instance of glorp is mutable and therefore unique// to the invocation of this function.var glorp = Character{.gold = 30,};// The "reward_xp" value is interesting. It's an immutable// value, so even though it is local, it can be put in global// data and shared between all invocations. But being such a// small value, it may also simply be inlined as a literal// value in your instruction code where it is used. It's up// to the compiler.const reward_xp: u32 = 200;// Now let's circle back around to that "std" struct we imported// at the top. Since it's just a regular Zig value once it's// imported, we can also assign new names for its fields and// declarations. "debug" refers to another struct and "print" is a// public function namespaced within THAT struct.//// Let's assign the std.debug.print function to a const named// "print" so that we can use this new name later!const print = ???;// Now let's look at assigning and pointing to values in Zig.//// We'll try three different ways of making a new name to access// our glorp Character and change one of its values.//// "glorp_access1" is incorrectly named! We asked Zig to set aside// memory for another Character struct. So when we assign glorp to// glorp_access1 here, we're actually assigning all of the fields// to make a copy! Now we have two separate characters.//// You don't need to fix this. But notice what gets printed in// your program's output for this one compared to the other two// assignments below!var glorp_access1: Character = glorp;glorp_access1.gold = 111;print("1:{}!. ", .{glorp.gold == glorp_access1.gold});// NOTE://// If we tried to do this with a const Character instead of a// var, changing the gold field would give us a compiler error// because const values are immutable!//// "glorp_access2" will do what we want. It points to the original// glorp's address. Also remember that we get one implicit// dereference with struct fields, so accessing the "gold" field// from glorp_access2 looks just like accessing it from glorp// itself.var glorp_access2: *Character = &glorp;glorp_access2.gold = 222;print("2:{}!. ", .{glorp.gold == glorp_access2.gold});// "glorp_access3" is interesting. It's also a pointer, but it's a// const. Won't that disallow changing the gold value? No! As you// may recall from our earlier pointer experiments, a constant// pointer can't change what it's POINTING AT, but the value at// the address it points to is still mutable! So we CAN change it.const glorp_access3: *Character = &glorp;glorp_access3.gold = 333;print("3:{}!. ", .{glorp.gold == glorp_access3.gold});// NOTE://// If we tried to do this with a *const Character pointer,// that would NOT work and we would get a compiler error// because the VALUE becomes immutable!//// Moving along...//// When arguments are passed to a function,// they are ALWAYS passed as constants within the function,// regardless of how they were declared in the calling function.//// Example:// fn foo(arg: u8) void {// arg = 42; // Error, 'arg' is const!// }//// fn bar() void {// var arg: u8 = 12;// foo(arg);// ...// }//// Knowing this, see if you can make levelUp() work as expected -// it should add the specified amount to the supplied character's// experience points.//print("XP before:{}, ", .{glorp.experience});// Fix 1 of 2 goes here:levelUp(glorp, reward_xp);print("after:{}.\n", .{glorp.experience});}// Fix 2 of 2 goes here:fn levelUp(character_access: Character, xp: u32) void {character_access.experience += xp;}// And there's more!//// Data segments (allocated at compile time) and "the stack"// (allocated at run time) aren't the only places where program data// can be stored in memory. They're just the most efficient. Sometimes// we don't know how much memory our program will need until the// program is running. Also, there is a limit to the size of stack// memory allotted to programs (often set by your operating system).// For these occasions, we have "the heap".//// You can use as much heap memory as you like (within physical// limitations, of course), but it's much less efficient to manage// because there is no built-in CPU support for adding and removing// items as we have with the stack. Also, depending on the type of// allocation, your program MAY have to do expensive work to manage// the use of heap memory. We'll learn about heap allocators later.//// Whew! This has been a lot of information. You'll be pleased to know// that the next exercise gets us back to learning Zig language// features we can use right away to do more things!
//// "We live on a placid island of ignorance in the midst// of black seas of infinity, and it was not meant that// we should voyage far."//// from The Call of Cthulhu// by H. P. Lovecraft//// Zig has at least four ways of expressing "no value"://// * undefined//// var foo: u8 = undefined;//// "undefined" should not be thought of as a value, but as a way// of telling the compiler that you are not assigning a value// _yet_. Any type may be set to undefined, but attempting// to read or use that value is _always_ a mistake.//// * null//// var foo: ?u8 = null;//// The "null" primitive value _is_ a value that means "no value".// This is typically used with optional types as with the ?u8// shown above. When foo equals null, that's not a value of type// u8. It means there is _no value_ of type u8 in foo at all!//// * error//// var foo: MyError!u8 = BadError;//// Errors are _very_ similar to nulls. They _are_ a value, but// they usually indicate that the "real value" you were looking// for does not exist. Instead, you have an error. The example// error union type of MyError!u8 means that foo either holds// a u8 value OR an error. There is _no value_ of type u8 in foo// when it's set to an error!//// * void//// var foo: void = {};//// "void" is a _type_, not a value. It is the most popular of the// Zero Bit Types (those types which take up absolutely no space// and have only a semantic value). When compiled to executable// code, zero bit types generate no code at all. The above example// shows a variable foo of type void which is assigned the value// of an empty expression. It's much more common to see void as// the return type of a function that returns nothing.//// Zig has all of these ways of expressing different types of "no value"// because they each serve a purpose. Briefly://// * undefined - there is no value YET, this cannot be read YET// * null - there is an explicit value of "no value"// * errors - there is no value because something went wrong// * void - there will NEVER be a value stored here//// Please use the correct "no value" for each ??? to make this program// print out a cursed quote from the Necronomicon. ...If you dare.//const std = @import("std");const Err = error{Cthulhu};pub fn main() void {var first_line1: *const [16]u8 = ???;first_line1 = "That is not dead";var first_line2: Err!*const [21]u8 = ???;first_line2 = "which can eternal lie";// Note we need the "{!s}" format for the error union string.std.debug.print("{s} {!s} / ", .{ first_line1, first_line2 });printSecondLine();}fn printSecondLine() ??? {var second_line2: ?*const [18]u8 = ???;second_line2 = "even death may die";std.debug.print("And with strange aeons {s}.\n", .{second_line2.?});}
//// "Trunks and tails// Are handy things"// from Holding Hands// by Lenore M. Link//// Now that we have tails all figured out, can you implement trunks?//const std = @import("std");const Elephant = struct {letter: u8,tail: ?*Elephant = null,trunk: ?*Elephant = null,visited: bool = false,// Elephant tail methods!pub fn getTail(self: *Elephant) *Elephant {return self.tail.?; // Remember, this means "orelse unreachable"}pub fn hasTail(self: *Elephant) bool {return (self.tail != null);}// Your Elephant trunk methods go here!// ---------------------------------------------------???// ---------------------------------------------------pub fn visit(self: *Elephant) void {self.visited = true;}pub fn print(self: *Elephant) void {// Prints elephant letter and [v]isitedconst v: u8 = if (self.visited) 'v' else ' ';std.debug.print("{u}{u} ", .{ self.letter, v });}};pub fn main() void {var elephantA = Elephant{ .letter = 'A' };var elephantB = Elephant{ .letter = 'B' };var elephantC = Elephant{ .letter = 'C' };// We link the elephants so that each tail "points" to the next.elephantA.tail = &elephantB;elephantB.tail = &elephantC;// And link the elephants so that each trunk "points" to the previous.elephantB.trunk = &elephantA;elephantC.trunk = &elephantB;visitElephants(&elephantA);std.debug.print("\n", .{});}// This function visits all elephants twice, tails to trunks.fn visitElephants(first_elephant: *Elephant) void {var e = first_elephant;// We follow the tails!while (true) {e.print();e.visit();// This gets the next elephant or stops.if (e.hasTail()) {e = e.getTail();} else {break;}}// We follow the trunks!while (true) {e.print();// This gets the previous elephant or stops.if (e.hasTrunk()) {e = e.getTrunk();} else {break;}}}
//// Now that we've seen how methods work, let's see if we can help// our elephants out a bit more with some Elephant methods.//const std = @import("std");const Elephant = struct {letter: u8,tail: ?*Elephant = null,visited: bool = false,// New Elephant methods!pub fn getTail(self: *Elephant) *Elephant {return self.tail.?; // Remember, this means "orelse unreachable"}pub fn hasTail(self: *Elephant) bool {return (self.tail != null);}pub fn visit(self: *Elephant) void {self.visited = true;}pub fn print(self: *Elephant) void {// Prints elephant letter and [v]isitedconst v: u8 = if (self.visited) 'v' else ' ';std.debug.print("{u}{u} ", .{ self.letter, v });}};pub fn main() void {var elephantA = Elephant{ .letter = 'A' };var elephantB = Elephant{ .letter = 'B' };var elephantC = Elephant{ .letter = 'C' };// This links the elephants so that each tail "points" to the next.elephantA.tail = &elephantB;elephantB.tail = &elephantC;visitElephants(&elephantA);std.debug.print("\n", .{});}// This function visits all elephants once, starting with the// first elephant and following the tails to the next elephant.fn visitElephants(first_elephant: *Elephant) void {var e = first_elephant;while (true) {e.print();e.visit();// This gets the next elephant or stops:// which method do we want here?e = if (e.hasTail()) e.??? else break;}}// Zig's enums can also have methods! This comment originally asked// if anyone could find instances of enum methods in the wild. The// first five pull requests were accepted and here they are://// 1) drforester - I found one in the Zig source:// https://github.com/ziglang/zig/blob/041212a41cfaf029dc3eb9740467b721c76f406c/src/Compilation.zig#L2495//// 2) bbuccianti - I found one!// https://github.com/ziglang/zig/blob/6787f163eb6db2b8b89c2ea6cb51d63606487e12/lib/std/debug.zig#L477//// 3) GoldsteinE - Found many, here's one// https://github.com/ziglang/zig/blob/ce14bc7176f9e441064ffdde2d85e35fd78977f2/lib/std/target.zig#L65//// 4) SpencerCDixon - Love this language so far :-)// https://github.com/ziglang/zig/blob/a502c160cd51ce3de80b3be945245b7a91967a85/src/zir.zig#L530//// 5) tomkun - here's another enum method// https://github.com/ziglang/zig/blob/4ca1f4ec2e3ae1a08295bc6ed03c235cb7700ab9/src/codegen/aarch64.zig#L24
//// Help! Evil alien creatures have hidden eggs all over the Earth// and they're starting to hatch!//// Before you jump into battle, you'll need to know three things://// 1. You can attach functions to structs (and other "type definitions")://// const Foo = struct{// pub fn hello() void {// std.debug.print("Foo says hello!\n", .{});// }// };//// 2. A function that is a member of a struct is "namespaced" within// that struct and is called by specifying the "namespace" and then// using the "dot syntax"://// Foo.hello();//// 3. The NEAT feature of these functions is that if their first argument// is an instance of the struct (or a pointer to one) then we can use// the instance as the namespace instead of the type://// const Bar = struct{// pub fn a(self: Bar) void {}// pub fn b(this: *Bar, other: u8) void {}// pub fn c(bar: *const Bar) void {}// };//// var bar = Bar{};// bar.a() // is equivalent to Bar.a(bar)// bar.b(3) // is equivalent to Bar.b(&bar, 3)// bar.c() // is equivalent to Bar.c(&bar)//// Notice that the name of the parameter doesn't matter. Some use// self, others use a lowercase version of the type name, but feel// free to use whatever is most appropriate.//// Okay, you're armed.//// Now, please zap the alien structs until they're all gone or// the Earth will be doomed!//const std = @import("std");// Look at this hideous Alien struct. Know your enemy!const Alien = struct {health: u8,// We hate this method:pub fn hatch(strength: u8) Alien {return Alien{.health = strength * 5,};}};// Your trusty weapon. Zap those aliens!const HeatRay = struct {damage: u8,// We love this method:pub fn zap(self: HeatRay, alien: *Alien) void {alien.health -= if (self.damage >= alien.health) alien.health else self.damage;}};pub fn main() void {// Look at all of these aliens of various strengths!var aliens = [_]Alien{Alien.hatch(2),Alien.hatch(1),Alien.hatch(3),Alien.hatch(3),Alien.hatch(5),Alien.hatch(3),};var aliens_alive = aliens.len;const heat_ray = HeatRay{ .damage = 7 }; // We've been given a heat ray weapon.// We'll keep checking to see if we've killed all the aliens yet.while (aliens_alive > 0) {aliens_alive = 0;// Loop through every alien by reference (* makes a pointer capture value)for (&aliens) |*alien| {// *** Zap the alien with the heat ray here! ***???.zap(???);// If the alien's health is still above 0, it's still alive.if (alien.health > 0) aliens_alive += 1;}std.debug.print("{} aliens. ", .{aliens_alive});}std.debug.print("Earth is saved!\n", .{});}
//// Now that we have optional types, we can apply them to structs.// The last time we checked in with our elephants, we had to link// all three of them together in a "circle" so that the last tail// linked to the first elephant. This is because we had NO CONCEPT// of a tail that didn't point to another elephant!//// We also introduce the handy `.?` shortcut://// const foo = bar.?;//// is the same as//// const foo = bar orelse unreachable;//// Check out where we use this shortcut below to change control flow// based on if an optional value exists.//// Now let's make those elephant tails optional!//const std = @import("std");const Elephant = struct {letter: u8,tail: *Elephant = null, // Hmm... tail needs something...visited: bool = false,};pub fn main() void {var elephantA = Elephant{ .letter = 'A' };var elephantB = Elephant{ .letter = 'B' };var elephantC = Elephant{ .letter = 'C' };// Link the elephants so that each tail "points" to the next.linkElephants(&elephantA, &elephantB);linkElephants(&elephantB, &elephantC);// `linkElephants` will stop the program if you try and link an// elephant that doesn't exist! Uncomment and see what happens.// const missingElephant: ?*Elephant = null;// linkElephants(&elephantC, missingElephant);visitElephants(&elephantA);std.debug.print("\n", .{});}// If e1 and e2 are valid pointers to elephants,// this function links the elephants so that e1's tail "points" to e2.fn linkElephants(e1: ?*Elephant, e2: ?*Elephant) void {e1.?.tail = e2.?;}// This function visits all elephants once, starting with the// first elephant and following the tails to the next elephant.fn visitElephants(first_elephant: *Elephant) void {var e = first_elephant;while (!e.visited) {std.debug.print("Elephant {u}. ", .{e.letter});e.visited = true;// We should stop once we encounter a tail that// does NOT point to another element. What can// we put here to make that happen?// HINT: We want something similar to what `.?` does,// but instead of ending the program, we want to exit the loop...e = e.tail ???}}
//// Sometimes you know that a variable might hold a value or// it might not. Zig has a neat way of expressing this idea// called Optionals. An optional type just has a '?' like this://// var foo: ?u32 = 10;//// Now foo can store a u32 integer OR null (a value storing// the cosmic horror of a value NOT EXISTING!)//// foo = null;//// if (foo == null) beginScreaming();//// Before we can use the optional value as the non-null type// (a u32 integer in this case), we need to guarantee that it// isn't null. One way to do this is to THREATEN IT with the// "orelse" statement.//// var bar = foo orelse 2;//// Here, bar will either equal the u32 integer value stored in// foo, or it will equal 2 if foo was null.//const std = @import("std");pub fn main() void {const result = deepThought();// Please threaten the result so that answer is either the// integer value from deepThought() OR the number 42:const answer: u8 = result;std.debug.print("The Ultimate Answer: {}.\n", .{answer});}fn deepThought() ?u8 {// It seems Deep Thought's output has declined in quality.// But we'll leave this as-is. Sorry Deep Thought.return null;}// Blast from the past://// Optionals are a lot like error union types which can either// hold a value or an error. Likewise, the orelse statement is// like the catch statement used to "unwrap" a value or supply// a default value://// var maybe_bad: Error!u32 = Error.Evil;// var number: u32 = maybe_bad catch 0;//
//// "Elephants walking// Along the trails//// Are holding hands// By holding tails."//// from Holding Hands// by Lenore M. Link//const std = @import("std");const Elephant = struct {letter: u8,tail: *Elephant = undefined,visited: bool = false,};pub fn main() void {var elephantA = Elephant{ .letter = 'A' };// (Please add Elephant B here!)var elephantC = Elephant{ .letter = 'C' };// Link the elephants so that each tail "points" to the next elephant.// They make a circle: A->B->C->A...elephantA.tail = &elephantB;// (Please link Elephant B's tail to Elephant C here!)elephantC.tail = &elephantA;visitElephants(&elephantA);std.debug.print("\n", .{});}// This function visits all elephants once, starting with the// first elephant and following the tails to the next elephant.// If we did not "mark" the elephants as visited (by setting// visited=true), then this would loop infinitely!fn visitElephants(first_elephant: *Elephant) void {var e = first_elephant;while (!e.visited) {std.debug.print("Elephant {u}. ", .{e.letter});e.visited = true;e = e.tail;}}
//// As with integers, you can pass a pointer to a struct when you// will wish to modify that struct. Pointers are also useful when// you need to store a reference to a struct (a "link" to it).//// const Vertex = struct{ x: u32, y: u32, z: u32 };//// var v1 = Vertex{ .x=3, .y=2, .z=5 };//// var pv: *Vertex = &v1; // <-- a pointer to our struct//// Note that you don't need to dereference the "pv" pointer to access// the struct's fields://// YES: pv.x// NO: pv.*.x//// We can write functions that take pointers to structs as// arguments. This foo() function modifies struct v://// fn foo(v: *Vertex) void {// v.x += 2;// v.y += 3;// v.z += 7;// }//// And call them like so://// foo(&v1);//// Let's revisit our RPG example and make a printCharacter() function// that takes a Character by reference and prints it...*and*// prints a linked "mentor" Character, if there is one.//const std = @import("std");const Class = enum {wizard,thief,bard,warrior,};const Character = struct {class: Class,gold: u32,health: u8 = 100, // You can provide default valuesexperience: u32,// I need to use the '?' here to allow for a null value. But// I don't explain it until later. Please don't tell anyone.mentor: ?*Character = null,};pub fn main() void {var mighty_krodor = Character{.class = Class.wizard,.gold = 10000,.experience = 2340,};var glorp = Character{ // Glorp!.class = Class.wizard,.gold = 10,.experience = 20,.mentor = &mighty_krodor, // Glorp's mentor is the Mighty Krodor};// FIX ME!// Please pass Glorp to printCharacter():printCharacter(???);}// Note how this function's "c" parameter is a pointer to a Character struct.fn printCharacter(c: *Character) void {// Here's something you haven't seen before: when switching an enum, you// don't have to write the full enum name. Zig understands that ".wizard"// means "Class.wizard" when we switch on a Class enum value:const class_name = switch (c.class) {.wizard => "Wizard",.thief => "Thief",.bard => "Bard",.warrior => "Warrior",};std.debug.print("{s} (G:{} H:{} XP:{})\n", .{class_name,c.gold,c.health,c.experience,});// Checking an "optional" value and capturing it will be// explained later (this pairs with the '?' mentioned above.)if (c.mentor) |mentor| {std.debug.print(" Mentor: ", .{});printCharacter(mentor);}}
//// Now let's use pointers to do something we haven't been// able to do before: pass a value by reference to a function.//// Why would we wish to pass a pointer to an integer variable// rather than the integer value itself? Because then we are// allowed to *change* the value of the variable!//// +-----------------------------------------------+// | Pass by reference when you want to change the |// | pointed-to value. Otherwise, pass the value. |// +-----------------------------------------------+//const std = @import("std");pub fn main() void {var num: u8 = 1;var more_nums = [_]u8{ 1, 1, 1, 1 };// Let's pass the num reference to our function and print it:makeFive(&num);std.debug.print("num: {}, ", .{num});// Now something interesting. Let's pass a reference to a// specific array value:makeFive(&more_nums[2]);// And print the array:std.debug.print("more_nums: ", .{});for (more_nums) |n| {std.debug.print("{} ", .{n});}std.debug.print("\n", .{});}// This function should take a reference to a u8 value and set it// to 5.fn makeFive(x: *u8) void {??? = 5; // fix me!}
//// The tricky part is that the pointer's mutability (var vs const) refers// to the ability to change what the pointer POINTS TO, not the ability// to change the VALUE at that location!//// const locked: u8 = 5;// var unlocked: u8 = 10;//// const p1: *const u8 = &locked;// var p2: *const u8 = &locked;//// Both p1 and p2 point to constant values which cannot change. However,// p2 can be changed to point to something else and p1 cannot!//// const p3: *u8 = &unlocked;// var p4: *u8 = &unlocked;// const p5: *const u8 = &unlocked;// var p6: *const u8 = &unlocked;//// Here p3 and p4 can both be used to change the value they point to but// p3 cannot point at anything else.// What's interesting is that p5 and p6 act like p1 and p2, but point to// the value at "unlocked". This is what we mean when we say that we can// make a constant reference to any value!//const std = @import("std");pub fn main() void {var foo: u8 = 5;var bar: u8 = 10;// Please define pointer "p" so that it can point to EITHER foo or// bar AND change the value it points to!??? p: ??? = undefined;p = &foo;p.* += 1;p = &bar;p.* += 1;std.debug.print("foo={}, bar={}\n", .{ foo, bar });}
//// It's important to note that variable pointers and constant pointers// are different types.//// Given://// var foo: u8 = 5;// const bar: u8 = 5;//// Then://// &foo is of type "*u8"// &bar is of type "*const u8"//// You can always make a const pointer to a mutable value (var), but// you cannot make a var pointer to an immutable value (const).// This sounds like a logic puzzle, but it just means that once data// is declared immutable, you can't coerce it to a mutable type.// Think of mutable data as being volatile or even dangerous. Zig// always lets you be "more safe" and never "less safe."//const std = @import("std");pub fn main() void {const a: u8 = 12;const b: *u8 = &a; // fix this!std.debug.print("a: {}, b: {}\n", .{ a, b.* });}
//// Check this out://// var foo: u8 = 5; // foo is 5// var bar: *u8 = &foo; // bar is a pointer//// What is a pointer? It's a reference to a value. In this example// bar is a reference to the memory space that currently contains the// value 5.//// A cheatsheet given the above declarations://// u8 the type of a u8 value// foo the value 5// *u8 the type of a pointer to a u8 value// &foo a reference to foo// bar a pointer to the value at foo// bar.* the value 5 (the dereferenced value "at" bar)//// We'll see why pointers are useful in a moment. For now, see if you// can make this example work!//const std = @import("std");pub fn main() void {var num1: u8 = 5;const num1_pointer: *u8 = &num1;var num2: u8 = undefined;// Please make num2 equal 5 using num1_pointer!// (See the "cheatsheet" above for ideas.)num2 = ???;std.debug.print("num1: {}, num2: {}\n", .{ num1, num2 });}
//// Grouping values in structs is not merely convenient. It also allows// us to treat the values as a single item when storing them, passing// them to functions, etc.//// This exercise demonstrates how we can store structs in an array and// how doing so lets us print them using a loop.//const std = @import("std");const Role = enum {wizard,thief,bard,warrior,};const Character = struct {role: Role,gold: u32,health: u8,experience: u32,};pub fn main() void {var chars: [2]Character = undefined;// Glorp the Wisechars[0] = Character{.role = Role.wizard,.gold = 20,.health = 100,.experience = 10,};// Please add "Zump the Loud" with the following properties://// role bard// gold 10// health 100// experience 20//// Feel free to run this program without adding Zump. What does// it do and why?// Printing all RPG characters in a loop:for (chars, 0..) |c, num| {std.debug.print("Character {} - G:{} H:{} XP:{}\n", .{num + 1, c.gold, c.health, c.experience,});}}// If you tried running the program without adding Zump as mentioned// above, you get what appear to be "garbage" values. In debug mode// (which is the default), Zig writes the repeating pattern "10101010"// in binary (or 0xAA in hex) to all undefined locations to make them// easier to spot when debugging.
//// Being able to group values together lets us turn this://// point1_x = 3;// point1_y = 16;// point1_z = 27;// point2_x = 7;// point2_y = 13;// point2_z = 34;//// into this://// point1 = Point{ .x=3, .y=16, .z=27 };// point2 = Point{ .x=7, .y=13, .z=34 };//// The Point above is an example of a "struct" (short for "structure").// Here's how that struct type could have been defined://// const Point = struct{ x: u32, y: u32, z: u32 };//// Let's store something fun with a struct: a roleplaying character!//const std = @import("std");// We'll use an enum to specify the character role.const Role = enum {wizard,thief,bard,warrior,};// Please add a new property to this struct called "health" and make// it a u8 integer type.const Character = struct {role: Role,gold: u32,experience: u32,};pub fn main() void {// Please initialize Glorp with 100 health.var glorp_the_wise = Character{.role = Role.wizard,.gold = 20,.experience = 10,};// Glorp gains some gold.glorp_the_wise.gold += 5;// Ouch! Glorp takes a punch!glorp_the_wise.health -= 10;std.debug.print("Your wizard has {} health and {} gold.\n", .{glorp_the_wise.health,glorp_the_wise.gold,});}
//// Enums are really just a set of numbers. You can leave the// numbering up to the compiler, or you can assign them// explicitly. You can even specify the numeric type used.//// const Stuff = enum(u8){ foo = 16 };//// You can get the integer out with a builtin function,// @intFromEnum(). We'll learn about builtins properly in a later// exercise.//// const my_stuff: u8 = @intFromEnum(Stuff.foo);//// Note how that built-in function starts with "@" just like the// @import() function we've been using.//const std = @import("std");// Zig lets us write integers in hexadecimal format://// 0xf (is the value 15 in hex)//// Web browsers let us specify colors using a hexadecimal// number where each byte represents the brightness of the// Red, Green, or Blue component (RGB) where two hex digits// are one byte with a value range of 0-255://// #RRGGBB//// Please define and use a pure blue value Color:const Color = enum(u32) {red = 0xff0000,green = 0x00ff00,blue = ???,};pub fn main() void {// Remember Zig's multi-line strings? Here they are again.// Also, check out this cool format string://// {x:0>6}// ^// x type ('x' is lower-case hexadecimal)// : separator (needed for format syntax)// 0 padding character (default is ' ')// > alignment ('>' aligns right)// 6 width (use padding to force width)//// Please add this formatting to the blue value.// (Even better, experiment without it, or try parts of it// to see what prints!)std.debug.print(\\<p>\\ <span style="color: #{x:0>6}">Red</span>\\ <span style="color: #{x:0>6}">Green</span>\\ <span style="color: #{}">Blue</span>\\</p>\\, .{@intFromEnum(Color.red),@intFromEnum(Color.green),@intFromEnum(???), // Oops! We're missing something!});}
//// Remember that little mathematical virtual machine we made using the// "unreachable" statement? Well, there were two problems with the// way we were using op codes://// 1. Having to remember op codes by number is no good.// 2. We had to use "unreachable" because Zig had no way of knowing// how many valid op codes there were.//// An "enum" is a Zig construct that lets you give names to numeric// values and store them in a set. They look a lot like error sets://// const Fruit = enum{ apple, pear, orange };//// const my_fruit = Fruit.apple;//// Let's use an enum in place of the numbers we were using in the// previous version!//const std = @import("std");// Please complete the enum!const Ops = enum { ??? };pub fn main() void {const operations = [_]Ops{Ops.inc,Ops.inc,Ops.inc,Ops.pow,Ops.dec,Ops.dec,};var current_value: u32 = 0;for (operations) |op| {switch (op) {Ops.inc => {current_value += 1;},Ops.dec => {current_value -= 1;},Ops.pow => {current_value *= current_value;},// No "else" needed! Why is that?}std.debug.print("{} ", .{current_value});}std.debug.print("\n", .{});}
//// Quiz time. See if you can make this program work!//// Solve this any way you like, just be sure the output is://// my_num=42//const std = @import("std");const NumError = error{IllegalNumber};pub fn main() void {var stdout = std.fs.File.stdout().writer(&.{});const my_num: u32 = getNumber();try stdout.interface.print("my_num={}\n", .{my_num});}// This function is obviously weird and non-functional. But you will not be changing it for this quiz.fn getNumber() NumError!u32 {if (false) return NumError.IllegalNumber;return 42;}
//// Let's revisit the very first error exercise. This time, we're going to// look at an error-handling variation of the "if" statement.//// if (foo) |value| {//// // foo was NOT an error; value is the non-error value of foo//// } else |err| {//// // foo WAS an error; err is the error value of foo//// }//// We'll take it even further and use a switch statement to handle// the error types.//// if (foo) |value| {// ...// } else |err| switch (err) {// ...// }//const MyNumberError = error{TooBig,TooSmall,};const std = @import("std");pub fn main() void {const nums = [_]u8{ 2, 3, 4, 5, 6 };for (nums) |num| {std.debug.print("{}", .{num});const n = numberMaybeFail(num);if (n) |value| {std.debug.print("={}. ", .{value});} else |err| switch (err) {MyNumberError.TooBig => std.debug.print(">4. ", .{}),// Please add a match for TooSmall here and have it print: "<4. "}}std.debug.print("\n", .{});}// This time we'll have numberMaybeFail() return an error union rather// than a straight error.fn numberMaybeFail(n: u8) MyNumberError!u8 {if (n > 4) return MyNumberError.TooBig;if (n < 4) return MyNumberError.TooSmall;return n;}
//// Zig has an "unreachable" statement. Use it when you want to tell the// compiler that a branch of code should never be executed and that the// mere act of reaching it is an error.//// if (true) {// ...// } else {// unreachable;// }//// Here we've made a little virtual machine that performs mathematical// operations on a single numeric value. It looks great but there's one// little problem: the switch statement doesn't cover every possible// value of a u8 number!//// WE know there are only three operations but Zig doesn't. Use the// unreachable statement to make the switch complete. Or ELSE. :-)//const std = @import("std");pub fn main() void {const operations = [_]u8{ 1, 1, 1, 3, 2, 2 };var current_value: u32 = 0;for (operations) |op| {switch (op) {1 => {current_value += 1;},2 => {current_value -= 1;},3 => {current_value *= current_value;},}std.debug.print("{} ", .{current_value});}std.debug.print("\n", .{});}
//// What's really nice is that you can use a switch statement as an// expression to return a value.//// const a = switch (x) {// 1 => 9,// 2 => 16,// 3 => 7,// ...// }//const std = @import("std");pub fn main() void {const lang_chars = [_]u8{ 26, 9, 7, 42 };for (lang_chars) |c| {const real_char: u8 = switch (c) {1 => 'A',2 => 'B',3 => 'C',4 => 'D',5 => 'E',6 => 'F',7 => 'G',8 => 'H',9 => 'I',10 => 'J',// ...25 => 'Y',26 => 'Z',// As in the last exercise, please add the 'else' clause// and this time, have it return an exclamation mark '!'.};std.debug.print("{c}", .{real_char});// Note: "{c}" forces print() to display the value as a character.// Can you guess what happens if you remove the "c"? Try it!}std.debug.print("\n", .{});}
//// The "switch" statement lets you match the possible values of an// expression and perform a different action for each.//// This switch://// switch (players) {// 1 => startOnePlayerGame(),// 2 => startTwoPlayerGame(),// else => {// alert();// return GameError.TooManyPlayers;// }// }//// Is equivalent to this if/else://// if (players == 1) startOnePlayerGame();// else if (players == 2) startTwoPlayerGame();// else {// alert();// return GameError.TooManyPlayers;// }//const std = @import("std");pub fn main() void {const lang_chars = [_]u8{ 26, 9, 7, 42 };for (lang_chars) |c| {switch (c) {1 => std.debug.print("A", .{}),2 => std.debug.print("B", .{}),3 => std.debug.print("C", .{}),4 => std.debug.print("D", .{}),5 => std.debug.print("E", .{}),6 => std.debug.print("F", .{}),7 => std.debug.print("G", .{}),8 => std.debug.print("H", .{}),9 => std.debug.print("I", .{}),10 => std.debug.print("J", .{}),// ... we don't need everything in between ...25 => std.debug.print("Y", .{}),26 => std.debug.print("Z", .{}),// Switch statements must be "exhaustive" (there must be a// match for every possible value). Please add an "else"// to this switch to print a question mark "?" when c is// not one of the existing matches.}}std.debug.print("\n", .{});}
//// Another common problem is a block of code that could exit in multiple// places due to an error - but that needs to do something before it// exits (typically to clean up after itself).//// An "errdefer" is a defer that only runs if the block exits with an error://// {// errdefer cleanup();// try canFail();// }//// The cleanup() function is called ONLY if the "try" statement returns an// error produced by canFail().//const std = @import("std");var counter: u32 = 0;const MyErr = error{ GetFail, IncFail };pub fn main() void {// We simply quit the entire program if we fail to get a number:const a: u32 = makeNumber() catch return;const b: u32 = makeNumber() catch return;std.debug.print("Numbers: {}, {}\n", .{ a, b });}fn makeNumber() MyErr!u32 {std.debug.print("Getting number...", .{});// Please make the "failed" message print ONLY if the makeNumber()// function exits with an error:std.debug.print("failed!\n", .{});var num = try getNumber(); // <-- This could fail!num = try increaseNumber(num); // <-- This could ALSO fail!std.debug.print("got {}. ", .{num});return num;}fn getNumber() MyErr!u32 {// I _could_ fail...but I don't!return 4;}fn increaseNumber(n: u32) MyErr!u32 {// I fail after the first time you run me!if (counter > 0) return MyErr.IncFail;// Sneaky, weird global stuff.counter += 1;return n + 1;}
//// Now that you know how "defer" works, let's do something more// interesting with it.//const std = @import("std");pub fn main() void {const animals = [_]u8{ 'g', 'c', 'd', 'd', 'g', 'z' };for (animals) |a| printAnimal(a);std.debug.print("done.\n", .{});}// This function is _supposed_ to print an animal name in parentheses// like "(Goat) ", but we somehow need to print the end parenthesis// even though this function can return in four different places!fn printAnimal(animal: u8) void {std.debug.print("(", .{});std.debug.print(") ", .{}); // <---- how?!if (animal == 'g') {std.debug.print("Goat", .{});return;}if (animal == 'c') {std.debug.print("Cat", .{});return;}if (animal == 'd') {std.debug.print("Dog", .{});return;}std.debug.print("Unknown", .{});}
//// You can assign some code to run _after_ a block of code exits by// deferring it with a "defer" statement://// {// defer runLater();// runNow();// }//// In the example above, runLater() will run when the block ({...})// is finished. So the code above will run in the following order://// runNow();// runLater();//// This feature seems strange at first, but we'll see how it could be// useful in the next exercise.const std = @import("std");pub fn main() void {// Without changing anything else, please add a 'defer' statement// to this code so that our program prints "One Two\n":std.debug.print("Two\n", .{});std.debug.print("One ", .{});}
//// Great news! Now we know enough to understand a "real" Hello World// program in Zig - one that uses the system Standard Out resource...which// can fail!//const std = @import("std");// Take note that this main() definition now returns "!void" rather// than just "void". Since there's no specific error type, this means// that Zig will infer the error type. This is appropriate in the case// of main(), but can make a function harder (function pointers) or// even impossible to work with (recursion) in some situations.//// You can find more information at:// https://ziglang.org/documentation/master/#Inferred-Error-Sets//pub fn main() !void {// We get a Writer for Standard Out so we can print() to it.var stdout = std.fs.File.stdout().writer(&.{});// Unlike std.debug.print(), the Standard Out writer can fail// with an error. We don't care _what_ the error is, we want// to be able to pass it up as a return value of main().//// We just learned of a single statement which can accomplish this.stdout.interface.print("Hello world!\n", .{});}
//// Zig has a handy "try" shortcut for this common error handling pattern://// canFail() catch |err| return err;//// which can be more compactly written as://// try canFail();//const std = @import("std");const MyNumberError = error{TooSmall,TooBig,};pub fn main() void {const a: u32 = addFive(44) catch 0;const b: u32 = addFive(14) catch 0;const c: u32 = addFive(4) catch 0;std.debug.print("a={}, b={}, c={}\n", .{ a, b, c });}fn addFive(n: u32) MyNumberError!u32 {// This function needs to return any error which might come back from detect().// Please use a "try" statement rather than a "catch".//const x = detect(n);return x + 5;}fn detect(n: u32) MyNumberError!u32 {if (n < 10) return MyNumberError.TooSmall;if (n > 20) return MyNumberError.TooBig;return n;}
//// Using `catch` to replace an error with a default value is a bit// of a blunt instrument since it doesn't matter what the error is.//// Catch lets us capture the error value and perform additional// actions with this form://// canFail() catch |err| {// if (err == FishError.TunaMalfunction) {// ...// }// };//const std = @import("std");const MyNumberError = error{TooSmall,TooBig,};pub fn main() void {// The "catch 0" below is a temporary hack to deal with// makeJustRight()'s returned error union (for now).const a: u32 = makeJustRight(44) catch 0;const b: u32 = makeJustRight(14) catch 0;const c: u32 = makeJustRight(4) catch 0;std.debug.print("a={}, b={}, c={}\n", .{ a, b, c });}// In this silly example we've split the responsibility of making// a number just right into four (!) functions://// makeJustRight() Calls fixTooBig(), cannot fix any errors.// fixTooBig() Calls fixTooSmall(), fixes TooBig errors.// fixTooSmall() Calls detectProblems(), fixes TooSmall errors.// detectProblems() Returns the number or an error.//fn makeJustRight(n: u32) MyNumberError!u32 {return fixTooBig(n) catch |err| {return err;};}fn fixTooBig(n: u32) MyNumberError!u32 {return fixTooSmall(n) catch |err| {if (err == MyNumberError.TooBig) {return 20;}return err;};}fn fixTooSmall(n: u32) MyNumberError!u32 {// Oh dear, this is missing a lot! But don't worry, it's nearly// identical to fixTooBig() above.//// If we get a TooSmall error, we should return 10.// If we get any other error, we should return that error.// Otherwise, we return the u32 number.return detectProblems(n) ???;}fn detectProblems(n: u32) MyNumberError!u32 {if (n < 10) return MyNumberError.TooSmall;if (n > 20) return MyNumberError.TooBig;return n;}
//// One way to deal with error unions is to "catch" any error and// replace it with a default value.//// foo = canFail() catch 6;//// If canFail() fails, foo will equal 6.//const std = @import("std");const MyNumberError = error{TooSmall};pub fn main() void {const a: u32 = addTwenty(44) catch 22;const b: u32 = addTwenty(4) ??? 22;std.debug.print("a={}, b={}\n", .{ a, b });}// Please provide the return type from this function.// Hint: it'll be an error union.fn addTwenty(n: u32) ??? {if (n < 5) {return MyNumberError.TooSmall;} else {return n + 20;}}
//// A common case for errors is a situation where we're expecting to// have a value OR something has gone wrong. Take this example://// var text: Text = getText("foo.txt");//// What happens if getText() can't find "foo.txt"? How do we express// this in Zig?//// Zig lets us make what's called an "error union" which is a value// which could either be a regular value OR an error from a set://// var text: MyErrorSet!Text = getText("foo.txt");//// For now, let's just see if we can try making an error union!//const std = @import("std");const MyNumberError = error{TooSmall};pub fn main() void {var my_number: ??? = 5;// Looks like my_number will need to either store a number OR// an error. Can you set the type correctly above?my_number = MyNumberError.TooSmall;std.debug.print("I compiled!\n", .{});}
//// Believe it or not, sometimes things go wrong in programs.//// In Zig, an error is a value. Errors are named so we can identify// things that can go wrong. Errors are created in "error sets", which// are just a collection of named errors.//// We have the start of an error set, but we're missing the condition// "TooSmall". Please add it where needed!const MyNumberError = error{TooBig,???,TooFour,};const std = @import("std");pub fn main() void {const nums = [_]u8{ 2, 3, 4, 5, 6 };for (nums) |n| {std.debug.print("{}", .{n});const number_error = numberFail(n);if (number_error == MyNumberError.TooBig) {std.debug.print(">4. ", .{});}if (???) {std.debug.print("<4. ", .{});}if (number_error == MyNumberError.TooFour) {std.debug.print("=4. ", .{});}}std.debug.print("\n", .{});}// Notice how this function can return any member of the MyNumberError// error set.fn numberFail(n: u8) MyNumberError {if (n > 4) return MyNumberError.TooBig;if (n < 4) return MyNumberError.TooSmall; // <---- this one is free!return MyNumberError.TooFour;}
//// Let's see if we can make use of some of the things we've learned so far.// We'll create two functions: one that contains a "for" loop and one// that contains a "while" loop.//// Both of these are simply labeled "loop" below.//const std = @import("std");pub fn main() void {const my_numbers = [4]u16{ 5, 6, 7, 8 };printPowersOfTwo(my_numbers);std.debug.print("\n", .{});}// You won't see this every day: a function that takes an array with// exactly four u16 numbers. This is not how you would normally pass// an array to a function. We'll learn about slices and pointers in// a little while. For now, we're using what we know.//// This function prints, but does not return anything.//fn printPowersOfTwo(numbers: [4]u16) ??? {loop (numbers) |n| {std.debug.print("{} ", .{twoToThe(n)});}}// This function bears a striking resemblance to twoToThe() in the last// exercise. But don't be fooled! This one does the math without the aid// of the standard library!//fn twoToThe(number: u16) ??? {var n: u16 = 0;var total: u16 = 1;loop (n < number) : (n += 1) {total *= 2;}return ???;}
//// Now let's create a function that takes a parameter. Here's an// example that takes two parameters. As you can see, parameters// are declared just like any other types ("name": "type")://// fn myFunction(number: u8, is_lucky: bool) {// ...// }//const std = @import("std");pub fn main() void {std.debug.print("Powers of two: {} {} {} {}\n", .{twoToThe(1),twoToThe(2),twoToThe(3),twoToThe(4),});}// Please give this function the correct input parameter(s).// You'll need to figure out the parameter name and type that we're// expecting. The output type has already been specified for you.//fn twoToThe(???) u32 {return std.math.pow(u32, 2, my_number);// std.math.pow(type, a, b) takes a numeric type and two// numbers of that type (or that can coerce to that type) and// returns "a to the power of b" as that same numeric type.}
//// Functions! We've already created lots of functions called 'main()'. Now let's// do something different://// fn foo(n: u8) u8 {// return n + 1;// }//// The foo() function above takes a number 'n' and returns a number that is// larger by one.//// Note the input parameter 'n' and return types are both u8.//const std = @import("std");pub fn main() void {// The new function deepThought() should return the number 42. See below.const answer: u8 = deepThought();std.debug.print("Answer to the Ultimate Question: {}\n", .{answer});}// Please define the deepThought() function below.//// We're just missing a couple things. One thing we're NOT missing is the// keyword "pub", which is not needed here. Can you guess why?//??? deepThought() ??? {return 42; // Number courtesy Douglas Adams}
//// Quiz time again! Let's see if you can solve the famous "Fizz Buzz"!//// "Players take turns to count incrementally, replacing// any number divisible by three with the word "fizz",// and any number divisible by five with the word "buzz".// - From https://en.wikipedia.org/wiki/Fizz_buzz//// Let's go from 1 to 16. This has been started for you, but there// are some problems. :-(//const std = import standard library;function main() void {var i: u8 = 1;const stop_at: u8 = 16;// What kind of loop is this? A 'for' or a 'while'???? (i <= stop_at) : (i += 1) {if (i % 3 == 0) std.debug.print("Fizz", .{});if (i % 5 == 0) std.debug.print("Buzz", .{});if (!(i % 3 == 0) and !(i % 5 == 0)) {std.debug.print("{}", .{???});}std.debug.print(", ", .{});}std.debug.print("\n", .{});}
//// For loops also let you use the "index" of the iteration, a number// that counts up with each iteration. To access the index of iteration,// specify a second condition as well as a second capture value.//// for (items, 0..) |item, index| {//// // Do something with item and index//// }//// You can name "item" and "index" anything you want. "i" is a popular// shortening of "index". The item name is often the singular form of// the items you're looping through.//const std = @import("std");pub fn main() void {// Let's store the bits of binary number 1101 in// 'little-endian' order (least significant byte or bit first):const bits = [_]u8{ 1, 0, 1, 1 };var value: u32 = 0;// Now we'll convert the binary bits to a number value by adding// the value of the place as a power of two for each bit.//// See if you can figure out the missing pieces:for (bits, ???) |bit, ???| {// Note that we convert the usize i to a u32 with// @intCast(), a builtin function just like @import().// We'll learn about these properly in a later exercise.const i_u32: u32 = @intCast(i);const place_value = std.math.pow(u32, 2, i_u32);value += place_value * bit;}std.debug.print("The value of bits '1101': {}.\n", .{value});}//// As mentioned in the previous exercise, 'for' loops have gained// additional flexibility since these early exercises were// written. As we'll see in later exercises, the above syntax for// capturing the index is part of a more general ability. Hang in// there!
//// Behold the 'for' loop! For loops let you execute code for each// element of an array://// for (items) |item| {//// // Do something with item//// }//const std = @import("std");pub fn main() void {const story = [_]u8{ 'h', 'h', 's', 'n', 'h' };std.debug.print("A Dramatic Story: ", .{});for (???) |???| {if (scene == 'h') std.debug.print(":-) ", .{});if (scene == 's') std.debug.print(":-( ", .{});if (scene == 'n') std.debug.print(":-| ", .{});}std.debug.print("The End.\n", .{});}// Note that 'for' loops also work on things called "slices"// which we'll see later.//// Also note that 'for' loops have recently become more flexible// and powerful (two years after this exercise was written).// More about that in a moment.
//// You can force a loop to exit immediately with a "break" statement://// while (condition) : (continue expression) {//// if (other condition) break;//// }//// Continue expressions do NOT execute when a while loop stops// because of a break!//const std = @import("std");pub fn main() void {var n: u32 = 1;// Oh dear! This while loop will go forever?!// Please fix this so the print statement below gives the desired output.while (true) : (n += 1) {if (???) ???;}// Result: we want n=4std.debug.print("n={}\n", .{n});}
//// The last two exercises were functionally identical. Continue// expressions really show their utility when used with 'continue'// statements!//// Example://// while (condition) : (continue expression) {//// if (other condition) continue;//// }//// The "continue expression" executes every time the loop restarts// whether the "continue" statement happens or not.//const std = @import("std");pub fn main() void {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) ???;if (n % 5 == 0) ???;std.debug.print("{} ", .{n});}std.debug.print("\n", .{});}
//// Zig 'while' statements can have an optional 'continue expression'// which runs every time the while loop continues (either at the// end of the loop or when an explicit 'continue' is invoked - we'll// try those out next)://// while (condition) : (continue expression) {// ...// }//// Example://// var foo = 2;// while (foo < 10) : (foo += 2) {// // Do something with even numbers less than 10...// }//// See if you can re-write the last exercise using a continue// expression://const std = @import("std");pub fn main() void {var n: u32 = 2;// Please set the continue expression so that we get the desired// results in the print statement below.while (n < 1000) : ??? {// Print the current numberstd.debug.print("{} ", .{n});}// As in the last exercise, we want this to result in "n=1024"std.debug.print("n={}\n", .{n});}
//// Zig 'while' statements create a loop that runs while the// condition is true. This runs once (at most)://// while (condition) {// condition = false;// }//// Remember that the condition must be a boolean value and// that we can get a boolean value from conditional operators// such as://// a == b means "a equals b"// a < b means "a is less than b"// a > b means "a is greater than b"// a != b means "a does not equal b"//const std = @import("std");pub fn main() void {var n: u32 = 2;// Please use a condition that is true UNTIL "n" reaches 1024:while (???) {// Print the current numberstd.debug.print("{} ", .{n});// Set n to n multiplied by 2n *= 2;}// Once the above is correct, this will print "n=1024"std.debug.print("n={}\n", .{n});}
//// If statements are also valid expressions://// const foo: u8 = if (a) 2 else 3;//const std = @import("std");pub fn main() void {const discount = true;// Please use an if...else expression to set "price".// If discount is true, the price should be $17, otherwise $20:const price: u8 = if ???;std.debug.print("With the discount, the price is ${}.\n", .{price});}
//// Now we get into the fun stuff, starting with the 'if' statement!//// if (true) {// ...// } else {// ...// }//// Zig has the "usual" comparison operators such as://// a == b means "a equals b"// a < b means "a is less than b"// a > b means "a is greater than b"// a != b means "a does not equal b"//// The important thing about Zig's "if" is that it *only* accepts// boolean values. It won't coerce numbers or other types of data// to true and false.//const std = @import("std");pub fn main() void {const foo = 1;// Please fix this condition:if (foo) {// We want our program to print this message!std.debug.print("Foo is 1!\n", .{});} else {std.debug.print("Foo is not 1!\n", .{});}}
//// Quiz time! Let's see if you can fix this whole program.//// You'll have to think about this one a bit.//// Let the compiler tell you what's wrong.//// Start at the top.//const std = @import("std");pub fn main() void {// What is this nonsense? :-)const letters = "YZhifg";// Note: usize is an unsigned integer type used for...sizes.// The exact size of usize depends on the target CPU// architecture. We could have used a u8 here, but usize is// the idiomatic type to use for array indexing.//// There IS a problem on this line, but 'usize' isn't it.const x: usize = 1;// Note: When you want to declare memory (an array in this// case) without putting anything in it, you can set it to// 'undefined'. There is no problem on this line.var lang: [3]u8 = undefined;// The following lines attempt to put 'Z', 'i', and 'g' into the// 'lang' array we just created by indexing the array// 'letters' with the variable 'x'. As you can see above, x=1// to begin with.lang[0] = letters[x];x = 3;lang[???] = letters[x];x = ???;lang[2] = letters[???];// We want to "Program in Zig!" of course:std.debug.print("Program in {s}!\n", .{lang});}
//// Here's a fun one: Zig has multi-line strings!//// To make a multi-line string, put '\\' at the beginning of each// line just like a code comment but with backslashes instead://// const two_lines =// \\Line One// \\Line Two// ;//// See if you can make this program print some song lyrics.//const std = @import("std");pub fn main() void {const lyrics =Ziggy played guitarJamming good with Andrew KelleyAnd the Spiders from Mars;std.debug.print("{s}\n", .{lyrics});}
//// Now that we've learned about arrays, we can talk about strings.//// We've already seen Zig string literals: "Hello world.\n"//// Zig stores strings as arrays of bytes.//// const foo = "Hello";//// Is almost* the same as://// const foo = [_]u8{ 'H', 'e', 'l', 'l', 'o' };//// (* We'll see what Zig strings REALLY are in Exercise 77.)//// Notice how individual characters use single quotes ('H') and// strings use double quotes ("H"). These are not interchangeable!//const std = @import("std");pub fn main() void {const ziggy = "stardust";// (Problem 1)// Use array square bracket syntax to get the letter 'd' from// the string "stardust" above.const d: u8 = ziggy[???];// (Problem 2)// Use the array repeat '**' operator to make "ha ha ha ".const laugh = "ha " ???;// (Problem 3)// Use the array concatenation '++' operator to make "Major Tom".// (You'll need to add a space as well!)const major = "Major";const tom = "Tom";const major_tom = major ??? tom;// That's all the problems. Let's see our results:std.debug.print("d={u} {s}{s}\n", .{ d, laugh, major_tom });// Keen eyes will notice that we've put 'u' and 's' inside the '{}'// placeholders in the format string above. This tells the// print() function to format the values as a UTF-8 character and// UTF-8 strings respectively. If we didn't do this, we'd see '100',// which is the decimal number corresponding with the 'd' character// in UTF-8. (And an error in the case of the strings.)//// While we're on this subject, 'c' (ASCII encoded character)// would work in place for 'u' because the first 128 characters// of UTF-8 are the same as ASCII!//}
//// Zig has some fun array operators.//// You can use '++' to concatenate two arrays://// const a = [_]u8{ 1,2 };// const b = [_]u8{ 3,4 };// const c = a ++ b ++ [_]u8{ 5 }; // equals 1 2 3 4 5//// You can use '**' to repeat an array://// const d = [_]u8{ 1,2,3 } ** 2; // equals 1 2 3 1 2 3//// Note that both '++' and '**' only operate on arrays while your// program is _being compiled_. This special time is known in Zig// parlance as "comptime" and we'll learn plenty more about that// later.//const std = @import("std");pub fn main() void {const le = [_]u8{ 1, 3 };const et = [_]u8{ 3, 7 };// (Problem 1)// Please set this array concatenating the two arrays above.// It should result in: 1 3 3 7const leet = ???;// (Problem 2)// Please set this array using repetition.// It should result in: 1 0 0 1 1 0 0 1 1 0 0 1const bit_pattern = [_]u8{ ??? } ** 3;// Okay, that's all of the problems. Let's see the results.//// We could print these arrays with leet[0], leet[1],...but let's// have a little preview of Zig 'for' loops instead://// for (<item array>) |item| { <do something with item> }//// Don't worry, we'll cover looping properly in upcoming// lessons.//std.debug.print("LEET: ", .{});for (leet) |n| {std.debug.print("{}", .{n});}std.debug.print(", Bits: ", .{});for (bit_pattern) |n| {std.debug.print("{}", .{n});}std.debug.print("\n", .{});}
//// Let's learn some array basics. Arrays are declared with://// var foo: [3]u32 = [3]u32{ 42, 108, 5423 };//// When Zig can infer the size of the array, you can use '_' for the// size. You can also let Zig infer the type of the value so the// declaration is much less verbose.//// var foo = [_]u32{ 42, 108, 5423 };//// Get values of an array using array[index] notation://// const bar = foo[2]; // 5423//// Set values of an array using array[index] notation://// foo[2] = 16;//// Get the length of an array using the len property://// const length = foo.len;//const std = @import("std");pub fn main() void {// (Problem 1)// This "const" is going to cause a problem later - can you see what it is?// How do we fix it?const some_primes = [_]u8{ 1, 3, 5, 7, 11, 13, 17, 19 };// Individual values can be set with '[]' notation.// Example: This line changes the first prime to 2 (which is correct):some_primes[0] = 2;// Individual values can also be accessed with '[]' notation.// Example: This line stores the first prime in "first":const first = some_primes[0];// (Problem 2)// Looks like we need to complete this expression. Use the example// above to set "fourth" to the fourth element of the some_primes array:const fourth = some_primes[???];// (Problem 3)// Use the len property to get the length of the array:const length = some_primes.???;std.debug.print("First: {}, Fourth: {}, Length: {}\n", .{first, fourth, length,});}
//// It seems we got a little carried away making everything "const u8"!//// "const" values cannot change.// "u" types are "unsigned" and cannot store negative values.// "8" means the type is 8 bits in size.//// Example: foo cannot change (it is CONSTant)// bar can change (it is VARiable)://// const foo: u8 = 20;// var bar: u8 = 20;//// Example: foo cannot be negative and can hold 0 to 255// bar CAN be negative and can hold -128 to 127//// const foo: u8 = 20;// const bar: i8 = -20;//// Example: foo can hold 8 bits (0 to 255)// bar can hold 16 bits (0 to 65,535)//// const foo: u8 = 20;// const bar: u16 = 2000;//// You can do just about any combination of these that you can think of://// u32 can hold 0 to 4,294,967,295// i64 can hold -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807//// Please fix this program so that the types can hold the desired values// and the errors go away!//const std = @import("std");pub fn main() void {const n: u8 = 50;n = n + 5;const pi: u8 = 314159;const negative_eleven: u8 = -11;// There are no errors in the next line, just explanation:// Perhaps you noticed before that the print function takes two// parameters. Now it will make more sense: the first parameter// is a string. The string may contain placeholders '{}', and the// second parameter is an "anonymous list literal" (don't worry// about this for now!) with the values to be printed.std.debug.print("{} {} {}\n", .{ n, pi, negative_eleven });}
//// Oops! This program is supposed to print a line like our Hello World// example. But we forgot how to import the Zig Standard Library.//// The @import() function is built into Zig. It returns a value which// represents the imported code. It's a good idea to store the import as// a constant value with the same name as the import://// const foo = @import("foo");//// Please complete the import below://??? = @import("std");pub fn main() void {std.debug.print("Standard Library.\n", .{});}// For the curious: Imports must be declared as constants because they// can only be used at compile time rather than run time. Zig evaluates// constant values at compile time. Don't worry, we'll cover imports// in detail later.// Also see this answer: https://stackoverflow.com/a/62567550/695615
//// Oh no, this is supposed to print "Hello world!" but it needs// your help.//// Zig functions are private by default but the main() function// should be public.//// A function is made public with the "pub" statement like so://// pub fn foo() void {// ...// }//// Perhaps knowing this will help solve the errors we're getting// with this little program?//const std = @import("std");fn main() void {std.debug.print("Hello world!\n", .{});}
const std = @import("std");const builtin = @import("builtin");const tests = @import("test/tests.zig");const Build = std.Build;const CompileStep = Build.CompileStep;const Step = Build.Step;const Child = std.process.Child;const assert = std.debug.assert;const join = std.fs.path.join;const print = std.debug.print;// When changing this version, be sure to also update README.md in two places:// 1) Getting Started// 2) Version Changescomptime {const required_zig = "0.15.0-dev.1519";const current_zig = builtin.zig_version;const min_zig = std.SemanticVersion.parse(required_zig) catch unreachable;if (current_zig.order(min_zig) == .lt) {const error_message =\\Sorry, it looks like your version of zig is too old. :-(\\\\Ziglings requires development build\\\\{}\\\\or higher.\\\\Please download a development ("master") build from\\\\https://ziglang.org/download/\\\\;@compileError(std.fmt.comptimePrint(error_message, .{min_zig}));}}const Kind = enum {/// Run the artifact as a normal executable.exe,/// Run the artifact as a test.@"test",};pub const Exercise = struct {/// main_file must have the format key_name.zig./// The key will be used as a shorthand to build just one example.main_file: []const u8,/// This is the desired output of the program./// A program passes if its output, excluding trailing whitespace, is equal/// to this string.output: []const u8,/// This is an optional hint to give if the program does not succeed.hint: ?[]const u8 = null,/// By default, we verify output against stderr./// Set this to true to check stdout instead.check_stdout: bool = false,/// This exercise makes use of C functions./// We need to keep track of this, so we compile with libc.link_libc: bool = false,/// This exercise kind.kind: Kind = .exe,/// This exercise is not supported by the current Zig compiler.skip: 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);}/// Returns the key of the main file, the string before the '_' with/// "zero padding" removed./// For example, "001_hello.zig" has the key "1".pub fn key(self: Exercise) []const u8 {// Main file must be key_description.zig.const end_index = std.mem.indexOfScalar(u8, self.main_file, '_') orelseunreachable;// Remove zero padding by advancing index past '0's.var start_index: usize = 0;while (self.main_file[start_index] == '0') start_index += 1;return self.main_file[start_index..end_index];}/// Returns the exercise key as an integer.pub fn number(self: Exercise) usize {return std.fmt.parseInt(usize, self.key(), 10) catch unreachable;}};/// Build mode.const Mode = enum {/// Normal build mode: `zig build`normal,/// Named build mode: `zig build -Dn=n`named,/// Random build mode: `zig build -Drandom`random,};pub const logo =\\ _ _ _\\ ___(_) __ _| (_)_ __ __ _ ___\\ |_ | |/ _' | | | '_ \ / _' / __|\\ / /| | (_| | | | | | | (_| \__ \\\ /___|_|\__, |_|_|_| |_|\__, |___/\\ |___/ |___/\\\\ "Look out! Broken programs below!"\\\\;const progress_filename = ".progress.txt";pub fn build(b: *Build) !void {if (!validate_exercises()) std.process.exit(2);use_color_escapes = false;if (std.fs.File.stderr().supportsAnsiEscapeCodes()) {use_color_escapes = true;} else if (builtin.os.tag == .windows) {const w32 = struct {const DWORD = std.os.windows.DWORD;const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;const STD_ERROR_HANDLE: DWORD = @bitCast(@as(i32, -12));const GetStdHandle = std.os.windows.kernel32.GetStdHandle;const GetConsoleMode = std.os.windows.kernel32.GetConsoleMode;const SetConsoleMode = std.os.windows.kernel32.SetConsoleMode;};const handle = w32.GetStdHandle(w32.STD_ERROR_HANDLE).?;var mode: w32.DWORD = 0;if (w32.GetConsoleMode(handle, &mode) != 0) {mode |= w32.ENABLE_VIRTUAL_TERMINAL_PROCESSING;use_color_escapes = w32.SetConsoleMode(handle, mode) != 0;}}if (use_color_escapes) {red_text = "\x1b[31m";red_bold_text = "\x1b[31;1m";red_dim_text = "\x1b[31;2m";green_text = "\x1b[32m";bold_text = "\x1b[1m";reset_text = "\x1b[0m";}// Remove the standard install and uninstall steps.b.top_level_steps = .{};const healed = b.option(bool, "healed", "Run exercises from patches/healed") orelsefalse;const override_healed_path = b.option([]const u8, "healed-path", "Override healed path");const exno: ?usize = b.option(usize, "n", "Select exercise");const rand: ?bool = b.option(bool, "random", "Select random exercise");const start: ?usize = b.option(usize, "s", "Start at exercise");const reset: ?bool = b.option(bool, "reset", "Reset exercise progress");const sep = std.fs.path.sep_str;const healed_path = if (override_healed_path) |path|pathelse"patches" ++ sep ++ "healed";const work_path = if (healed) healed_path else "exercises";const header_step = PrintStep.create(b, logo);if (exno) |n| {// Named build mode: verifies a single exercise.if (n == 0 or n > exercises.len - 1) {print("unknown exercise number: {}\n", .{n});std.process.exit(2);}const ex = exercises[n - 1];const zigling_step = b.step("zigling",b.fmt("Check the solution of {s}", .{ex.main_file}),);b.default_step = zigling_step;zigling_step.dependOn(&header_step.step);const verify_step = ZiglingStep.create(b, ex, work_path, .named);verify_step.step.dependOn(&header_step.step);zigling_step.dependOn(&verify_step.step);return;}if (rand) |_| {// Random build mode: verifies one random exercise.// like for 'exno' but chooses a random exercise number.print("work in progress: check a random exercise\n", .{});var prng = std.Random.DefaultPrng.init(blk: {var seed: u64 = undefined;try std.posix.getrandom(std.mem.asBytes(&seed));break :blk seed;});const rnd = prng.random();const ex = exercises[rnd.intRangeLessThan(usize, 0, exercises.len)];print("random exercise: {s}\n", .{ex.main_file});const zigling_step = b.step("random",b.fmt("Check the solution of {s}", .{ex.main_file}),);b.default_step = zigling_step;zigling_step.dependOn(&header_step.step);const verify_step = ZiglingStep.create(b, ex, work_path, .random);verify_step.step.dependOn(&header_step.step);zigling_step.dependOn(&verify_step.step);return;}if (start) |s| {if (s == 0 or s > exercises.len - 1) {print("unknown exercise number: {}\n", .{s});std.process.exit(2);}const first = exercises[s - 1];const ziglings_step = b.step("ziglings", b.fmt("Check ziglings starting with {s}", .{first.main_file}));b.default_step = ziglings_step;var prev_step = &header_step.step;for (exercises[(s - 1)..]) |ex| {const verify_stepn = ZiglingStep.create(b, ex, work_path, .normal);verify_stepn.step.dependOn(prev_step);prev_step = &verify_stepn.step;}ziglings_step.dependOn(prev_step);return;}if (reset) |_| {std.fs.cwd().deleteFile(progress_filename) catch |err| {switch (err) {std.fs.Dir.DeleteFileError.FileNotFound => {},else => {print("Unable to remove progress file, Error: {}\n", .{err});return err;},}};print("Progress reset, {s} removed.\n", .{progress_filename});std.process.exit(0);}// Normal build mode: verifies all exercises according to the recommended// order.const ziglings_step = b.step("ziglings", "Check all ziglings");b.default_step = ziglings_step;var prev_step = &header_step.step;var starting_exercise: u32 = 0;if (std.fs.cwd().openFile(progress_filename, .{})) |progress_file| {defer progress_file.close();const progress_file_size = try progress_file.getEndPos();var gpa = std.heap.GeneralPurposeAllocator(.{}){};defer _ = gpa.deinit();const allocator = gpa.allocator();const contents = try progress_file.readToEndAlloc(allocator, progress_file_size);defer allocator.free(contents);starting_exercise = try std.fmt.parseInt(u32, contents, 10);} else |err| {switch (err) {std.fs.File.OpenError.FileNotFound => {// This is fine, may be the first time tests are run or progress have been reset},else => {print("Unable to open {s}: {}\n", .{ progress_filename, err });return err;},}}for (exercises) |ex| {if (starting_exercise < ex.number()) {const verify_stepn = ZiglingStep.create(b, ex, work_path, .normal);verify_stepn.step.dependOn(prev_step);prev_step = &verify_stepn.step;}}ziglings_step.dependOn(prev_step);const test_step = b.step("test", "Run all the tests");test_step.dependOn(tests.addCliTests(b, &exercises));}var use_color_escapes = false;var red_text: []const u8 = "";var red_bold_text: []const u8 = "";var red_dim_text: []const u8 = "";var green_text: []const u8 = "";var bold_text: []const u8 = "";var reset_text: []const u8 = "";const ZiglingStep = struct {step: Step,exercise: Exercise,work_path: []const u8,mode: Mode,pub fn create(b: *Build,exercise: Exercise,work_path: []const u8,mode: Mode,) *ZiglingStep {const self = b.allocator.create(ZiglingStep) catch @panic("OOM");self.* = .{.step = Step.init(.{.id = .custom,.name = exercise.main_file,.owner = b,.makeFn = make,}),.exercise = exercise,.work_path = work_path,.mode = mode,};return self;}fn make(step: *Step, options: Step.MakeOptions) !void {// NOTE: Using exit code 2 will prevent the Zig compiler to print the message:// "error: the following build command failed with exit code 1:..."const self: *ZiglingStep = @alignCast(@fieldParentPtr("step", step));if (self.exercise.skip) {print("Skipping {s}\n\n", .{self.exercise.main_file});return;}const exe_path = self.compile(options.progress_node) catch {self.printErrors();if (self.exercise.hint) |hint|print("\n{s}Ziglings hint: {s}{s}", .{ bold_text, hint, reset_text });self.help();std.process.exit(2);};self.run(exe_path, options.progress_node) catch {self.printErrors();if (self.exercise.hint) |hint|print("\n{s}Ziglings hint: {s}{s}", .{ bold_text, hint, reset_text });self.help();std.process.exit(2);};// Print possible warning/debug messages.self.printErrors();}fn run(self: *ZiglingStep, exe_path: []const u8, _: std.Progress.Node) !void {resetLine();print("Checking: {s}\n", .{self.exercise.main_file});const b = self.step.owner;// Allow up to 1 MB of stdout capture.const max_output_bytes = 1 * 1024 * 1024;const result = Child.run(.{.allocator = b.allocator,.argv = &.{exe_path},.cwd = b.build_root.path.?,.cwd_dir = b.build_root.handle,.max_output_bytes = max_output_bytes,}) catch |err| {return self.step.fail("unable to spawn {s}: {s}", .{exe_path, @errorName(err),});};switch (self.exercise.kind) {.exe => return self.check_output(result),.@"test" => return self.check_test(result),}}fn check_output(self: *ZiglingStep, result: Child.RunResult) !void {const b = self.step.owner;// Make sure it exited cleanly.switch (result.term) {.Exited => |code| {if (code != 0) {return self.step.fail("{s} exited with error code {d} (expected {})", .{self.exercise.main_file, code, 0,});}},else => {return self.step.fail("{s} terminated unexpectedly", .{self.exercise.main_file,});},}const raw_output = if (self.exercise.check_stdout)result.stdoutelseresult.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)) {const red = red_bold_text;const reset = reset_text;// Override the coloring applied by the printError method.// NOTE: the first red and the last reset are not necessary, they// are here only for alignment.return self.step.fail(\\\\{s}========= expected this output: =========={s}\\{s}\\{s}========= but found: ====================={s}\\{s}\\{s}=========================================={s}, .{ red, reset, exercise_output, red, reset, output, red, reset });}const progress = try std.fmt.allocPrint(b.allocator, "{d}", .{self.exercise.number()});defer b.allocator.free(progress);const file = try std.fs.cwd().createFile(progress_filename,.{ .read = true, .truncate = true },);defer file.close();try file.writeAll(progress);try file.sync();print("{s}PASSED:\n{s}{s}\n\n", .{ green_text, output, reset_text });}fn check_test(self: *ZiglingStep, result: Child.RunResult) !void {switch (result.term) {.Exited => |code| {if (code != 0) {// The test failed.const stderr = std.mem.trimRight(u8, result.stderr, " \r\n");return self.step.fail("\n{s}", .{stderr});}},else => {return self.step.fail("{s} terminated unexpectedly", .{self.exercise.main_file,});},}print("{s}PASSED{s}\n\n", .{ green_text, reset_text });}fn compile(self: *ZiglingStep, prog_node: std.Progress.Node) ![]const u8 {print("Compiling: {s}\n", .{self.exercise.main_file});const b = self.step.owner;const exercise_path = self.exercise.main_file;const path = join(b.allocator, &.{ self.work_path, exercise_path }) catch@panic("OOM");var zig_args = std.array_list.Managed([]const u8).init(b.allocator);defer zig_args.deinit();zig_args.append(b.graph.zig_exe) catch @panic("OOM");const cmd = switch (self.exercise.kind) {.exe => "build-exe",.@"test" => "test",};zig_args.append(cmd) catch @panic("OOM");// Enable C support for exercises that use C functions.if (self.exercise.link_libc) {zig_args.append("-lc") catch @panic("OOM");}zig_args.append(b.pathFromRoot(path)) catch @panic("OOM");zig_args.append("--cache-dir") catch @panic("OOM");zig_args.append(b.pathFromRoot(b.cache_root.path.?)) catch @panic("OOM");zig_args.append("--listen=-") catch @panic("OOM");//// NOTE: After many changes in zig build system, we need to create the cache path manually.// See https://github.com/ziglang/zig/pull/21115// Maybe there is a better way (in the future).const exe_dir = try self.step.evalZigProcess(zig_args.items, prog_node, false, null, b.allocator);const exe_name = switch (self.exercise.kind) {.exe => self.exercise.name(),.@"test" => "test",};const sep = std.fs.path.sep_str;const root_path = exe_dir.?.root_dir.path.?;const sub_path = exe_dir.?.subPathOrDot();const exe_path = b.fmt("{s}{s}{s}{s}{s}", .{ root_path, sep, sub_path, sep, exe_name });return exe_path;}fn help(self: *ZiglingStep) void {const b = self.step.owner;const key = self.exercise.key();const path = self.exercise.main_file;const cmd = switch (self.mode) {.normal => "zig build",.named => b.fmt("zig build -Dn={s}", .{key}),.random => "zig build -Drandom",};print("\n{s}Edit exercises/{s} and run '{s}' again.{s}\n", .{red_bold_text, path, cmd, reset_text,});}fn printErrors(self: *ZiglingStep) void {resetLine();// Display error/warning messages.if (self.step.result_error_msgs.items.len > 0) {for (self.step.result_error_msgs.items) |msg| {print("{s}error: {s}{s}{s}{s}\n", .{red_bold_text, reset_text, red_dim_text, msg, reset_text,});}}// Render compile errors at the bottom of the terminal.// TODO: use the same ttyconf from the builder.const ttyconf: std.io.tty.Config = if (use_color_escapes).escape_codeselse.no_color;if (self.step.result_error_bundle.errorMessageCount() > 0) {self.step.result_error_bundle.renderToStdErr(.{ .ttyconf = ttyconf });}}};/// Clears the entire line and move the cursor to column zero./// Used for clearing the compiler and build_runner progress messages.fn resetLine() void {if (use_color_escapes) print("{s}", .{"\x1b[2K\r"});}/// Removes trailing whitespace for each line in buf, also ensuring that there/// are no trailing LF characters at the end.pub fn trimLines(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 {var list = try std.array_list.Aligned(u8, null).initCapacity(allocator, buf.len);var iter = std.mem.splitSequence(u8, buf, " \n");while (iter.next()) |line| {// TODO: trimming CR characters is probably not necessary.const data = std.mem.trimRight(u8, line, " \r");try list.appendSlice(allocator, data);try list.append(allocator, '\n');}const result = try list.toOwnedSlice(allocator); // TODO: probably not necessary// Remove the trailing LF character, that is always present in the exercise// output.return std.mem.trimRight(u8, result, "\n");}/// Prints a message to stderr.const PrintStep = struct {step: Step,message: []const u8,pub fn create(owner: *Build, message: []const u8) *PrintStep {const self = owner.allocator.create(PrintStep) catch @panic("OOM");self.* = .{.step = Step.init(.{.id = .custom,.name = "print",.owner = owner,.makeFn = make,}),.message = message,};return self;}fn make(step: *Step, _: Step.MakeOptions) !void {const self: *PrintStep = @alignCast(@fieldParentPtr("step", step));print("{s}", .{self.message});}};/// Checks that each exercise number, excluding the last, forms the sequence/// `[1, exercise.len)`.////// Additionally check that the output field lines doesn't have trailing whitespace.fn validate_exercises() bool {// Don't use the "multi-object for loop" syntax, in order to avoid a syntax// error with old Zig compilers.var i: usize = 0;for (exercises[0..]) |ex| {const exno = ex.number();const last = 999;i += 1;if (exno != i and exno != last) {print("exercise {s} has an incorrect number: expected {}, got {s}\n", .{ex.main_file, i, ex.key(),});return false;}var iter = std.mem.splitScalar(u8, ex.output, '\n');while (iter.next()) |line| {const output = std.mem.trimRight(u8, line, " \r");if (output.len != line.len) {print("exercise {s} output field lines have trailing whitespace\n", .{ex.main_file,});return false;}}if (!std.mem.endsWith(u8, ex.main_file, ".zig")) {print("exercise {s} is not a zig source file\n", .{ex.main_file});return false;}}return true;}const exercises = [_]Exercise{.{.main_file = "001_hello.zig",.output = "Hello world!",.hint =\\DON'T PANIC!\\Read the compiler messages above. (Something about 'main'?)\\Open up the source file as noted below and read the comments.\\\\(Hints like these will occasionally show up, but for the\\most part, you'll be taking directions from the Zig\\compiler itself.)\\,},.{.main_file = "002_std.zig",.output = "Standard Library.",},.{.main_file = "003_assignment.zig",.output = "55 314159 -11",.hint = "There are three mistakes in this one!",},.{.main_file = "004_arrays.zig",.output = "First: 2, Fourth: 7, Length: 8",.hint = "There are two things to complete here.",},.{.main_file = "005_arrays2.zig",.output = "LEET: 1337, Bits: 100110011001",.hint = "Fill in the two arrays.",},.{.main_file = "006_strings.zig",.output = "d=d ha ha ha Major Tom",.hint = "Each '???' needs something filled in.",},.{.main_file = "007_strings2.zig",.output =\\Ziggy played guitar\\Jamming good with Andrew Kelley\\And the Spiders from Mars,.hint = "Please fix the lyrics!",},.{.main_file = "008_quiz.zig",.output = "Program in Zig!",.hint = "See if you can fix the program!",},.{.main_file = "009_if.zig",.output = "Foo is 1!",},.{.main_file = "010_if2.zig",.output = "With the discount, the price is $17.",},.{.main_file = "011_while.zig",.output = "2 4 8 16 32 64 128 256 512 n=1024",.hint = "You probably want a 'less than' condition.",},.{.main_file = "012_while2.zig",.output = "2 4 8 16 32 64 128 256 512 n=1024",.hint = "It might help to look back at the previous exercise.",},.{.main_file = "013_while3.zig",.output = "1 2 4 7 8 11 13 14 16 17 19",},.{.main_file = "014_while4.zig",.output = "n=4",},.{.main_file = "015_for.zig",.output = "A Dramatic Story: :-) :-) :-( :-| :-) The End.",},.{.main_file = "016_for2.zig",.output = "The value of bits '1101': 13.",},.{.main_file = "017_quiz2.zig",.output = "1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz, 16,",.hint = "This is a famous game!",},.{.main_file = "018_functions.zig",.output = "Answer to the Ultimate Question: 42",.hint = "Can you help write the function?",},.{.main_file = "019_functions2.zig",.output = "Powers of two: 2 4 8 16",},.{.main_file = "020_quiz3.zig",.output = "32 64 128 256",.hint = "Unexpected pop quiz! Help!",},.{.main_file = "021_errors.zig",.output = "2<4. 3<4. 4=4. 5>4. 6>4.",.hint = "What's the deal with fours?",},.{.main_file = "022_errors2.zig",.output = "I compiled!",.hint = "Get the error union type right to allow this to compile.",},.{.main_file = "023_errors3.zig",.output = "a=64, b=22",},.{.main_file = "024_errors4.zig",.output = "a=20, b=14, c=10",},.{.main_file = "025_errors5.zig",.output = "a=0, b=19, c=0",},.{.main_file = "026_hello2.zig",.output = "Hello world!",.hint = "Try using a try!",.check_stdout = true,},.{.main_file = "027_defer.zig",.output = "One Two",},.{.main_file = "028_defer2.zig",.output = "(Goat) (Cat) (Dog) (Dog) (Goat) (Unknown) done.",},.{.main_file = "029_errdefer.zig",.output = "Getting number...got 5. Getting number...failed!",},.{.main_file = "030_switch.zig",.output = "ZIG?",},.{.main_file = "031_switch2.zig",.output = "ZIG!",},.{.main_file = "032_unreachable.zig",.output = "1 2 3 9 8 7",},.{.main_file = "033_iferror.zig",.output = "2<4. 3<4. 4=4. 5>4. 6>4.",.hint = "Seriously, what's the deal with fours?",},.{.main_file = "034_quiz4.zig",.output = "my_num=42",.hint = "Can you make this work?",.check_stdout = true,},.{.main_file = "035_enums.zig",.output = "1 2 3 9 8 7",.hint = "This problem seems familiar...",},.{.main_file = "036_enums2.zig",.output =\\<p>\\ <span style="color: #ff0000">Red</span>\\ <span style="color: #00ff00">Green</span>\\ <span style="color: #0000ff">Blue</span>\\</p>,.hint = "I'm feeling blue about this.",},.{.main_file = "037_structs.zig",.output = "Your wizard has 90 health and 25 gold.",},.{.main_file = "038_structs2.zig",.output =\\Character 1 - G:20 H:100 XP:10\\Character 2 - G:10 H:100 XP:20,},.{.main_file = "039_pointers.zig",.output = "num1: 5, num2: 5",.hint = "Pointers aren't so bad.",},.{.main_file = "040_pointers2.zig",.output = "a: 12, b: 12",},.{.main_file = "041_pointers3.zig",.output = "foo=6, bar=11",},.{.main_file = "042_pointers4.zig",.output = "num: 5, more_nums: 1 1 5 1",},.{.main_file = "043_pointers5.zig",.output =\\Wizard (G:10 H:100 XP:20)\\ Mentor: Wizard (G:10000 H:100 XP:2340),},.{.main_file = "044_quiz5.zig",.output = "Elephant A. Elephant B. Elephant C.",.hint = "Oh no! We forgot Elephant B!",},.{.main_file = "045_optionals.zig",.output = "The Ultimate Answer: 42.",},.{.main_file = "046_optionals2.zig",.output = "Elephant A. Elephant B. Elephant C.",.hint = "Elephants again!",},.{.main_file = "047_methods.zig",.output = "5 aliens. 4 aliens. 1 aliens. 0 aliens. Earth is saved!",.hint = "Use the heat ray. And the method!",},.{.main_file = "048_methods2.zig",.output = "A B C",.hint = "This just needs one little fix.",},.{.main_file = "049_quiz6.zig",.output = "A B C Cv Bv Av",.hint = "Now you're writing Zig!",},.{.main_file = "050_no_value.zig",.output = "That is not dead which can eternal lie / And with strange aeons even death may die.",},.{.main_file = "051_values.zig",.output = "1:false!. 2:true!. 3:true!. XP before:0, after:200.",},.{.main_file = "052_slices.zig",.output =\\Hand1: A 4 K 8\\Hand2: 5 2 Q J,},.{.main_file = "053_slices2.zig",.output = "'all your base are belong to us.' 'for great justice.'",},.{.main_file = "054_manypointers.zig",.output = "Memory is a resource.",},.{.main_file = "055_unions.zig",.output = "Insect report! Ant alive is: true. Bee visited 15 flowers.",},.{.main_file = "056_unions2.zig",.output = "Insect report! Ant alive is: true. Bee visited 16 flowers.",},.{.main_file = "057_unions3.zig",.output = "Insect report! Ant alive is: true. Bee visited 17 flowers.",},.{.main_file = "058_quiz7.zig",.output = "Archer's Point--2->Bridge--1->Dogwood Grove--3->Cottage--2->East Pond--1->Fox Pond",.hint = "This is the biggest program we've seen yet. But you can do it!",},.{.main_file = "059_integers.zig",.output = "Zig is cool.",},.{.main_file = "060_floats.zig",.output = "Shuttle liftoff weight: 2032 metric tons",},.{.main_file = "061_coercions.zig",.output = "Letter: A",},.{.main_file = "062_loop_expressions.zig",.output = "Current language: Zig",.hint = "Surely the current language is 'Zig'!",},.{.main_file = "063_labels.zig",.output = "Enjoy your Cheesy Chili!",},.{.main_file = "064_builtins.zig",.output = "1101 + 0101 = 0010 (true). Without overflow: 00010010. Furthermore, 11110000 backwards is 00001111.",},.{.main_file = "065_builtins2.zig",.output = "A Narcissus loves all Narcissuses. He has room in his heart for: me myself.",},.{.main_file = "066_comptime.zig",.output = "Immutable: 12345, 987.654; Mutable: 54321, 456.789; Types: comptime_int, comptime_float, u32, f32",.hint = "It may help to read this one out loud to your favorite stuffed animal until it sinks in completely.",},.{.main_file = "067_comptime2.zig",.output = "A BB CCC DDDD",},.{.main_file = "068_comptime3.zig",.output =\\Minnow (1:32, 4 x 2)\\Shark (1:16, 8 x 5)\\Whale (1:1, 143 x 95),},.{.main_file = "069_comptime4.zig",.output = "s1={ 1, 2, 3 }, s2={ 1, 2, 3, 4, 5 }, s3={ 1, 2, 3, 4, 5, 6, 7 }",},.{.main_file = "070_comptime5.zig",.output =\\"Quack." ducky1: true, "Squeek!" ducky2: true, ducky3: false,.hint = "Have you kept the wizard hat on?",},.{.main_file = "071_comptime6.zig",.output = "Narcissus has room in his heart for: me myself.",},.{.main_file = "072_comptime7.zig",.output = "26",},.{.main_file = "073_comptime8.zig",.output = "My llama value is 25.",},.{.main_file = "074_comptime9.zig",.output = "My llama value is 2.",.skip = true,},.{.main_file = "075_quiz8.zig",.output = "Archer's Point--2->Bridge--1->Dogwood Grove--3->Cottage--2->East Pond--1->Fox Pond",.hint = "Roll up those sleeves. You get to WRITE some code for this one.",},.{.main_file = "076_sentinels.zig",.output = "Array:123056. Many-item pointer:123.",},.{.main_file = "077_sentinels2.zig",.output = "Weird Data!",},.{.main_file = "078_sentinels3.zig",.output = "Weird Data!",},.{.main_file = "079_quoted_identifiers.zig",.output = "Sweet freedom: 55, false.",.hint = "Help us, Zig Programmer, you're our only hope!",},.{.main_file = "080_anonymous_structs.zig",.output = "[Circle(i32): 25,70,15] [Circle(f32): 25.2,71.0,15.7]",},.{.main_file = "081_anonymous_structs2.zig",.output = "x:205 y:187 radius:12",},.{.main_file = "082_anonymous_structs3.zig",.output =\\"0"(bool):true "1"(bool):false "2"(i32):42 "3"(f32):3.141592,.hint = "This one is a challenge! But you have everything you need.",},.{.main_file = "083_anonymous_lists.zig",.output = "I say hello!",},// 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,},.{.main_file = "085_async2.zig",.output = "Hello async!",.skip = true,},.{.main_file = "086_async3.zig",.output = "5 4 3 2 1",.skip = true,},.{.main_file = "087_async4.zig",.output = "1 2 3 4 5",.skip = true,},.{.main_file = "088_async5.zig",.output = "Example Title.",.skip = true,},.{.main_file = "089_async6.zig",.output = ".com: Example Title, .org: Example Title.",.skip = true,},.{.main_file = "090_async7.zig",.output = "beef? BEEF!",.skip = true,},.{.main_file = "091_async8.zig",.output = "ABCDEF",.skip = true,},.{.main_file = "092_interfaces.zig",.output =\\Daily Insect Report:\\Ant is alive.\\Bee visited 17 flowers.\\Grasshopper hopped 32 meters.,},.{.main_file = "093_hello_c.zig",.output = "Hello C from Zig! - C result is 17 chars written.",.link_libc = true,},.{.main_file = "094_c_math.zig",.output = "The normalized angle of 765.2 degrees is 45.2 degrees.",.link_libc = true,},.{.main_file = "095_for3.zig",.output = "1 2 4 7 8 11 13 14 16 17 19",},.{.main_file = "096_memory_allocation.zig",.output = "Running Average: 0.30 0.25 0.20 0.18 0.22",},.{.main_file = "097_bit_manipulation.zig",.output = "x = 1011; y = 1101",},.{.main_file = "098_bit_manipulation2.zig",.output = "Is this a pangram? true!",},.{.main_file = "099_formatting.zig",.output =\\\\ X | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15\\---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\\ 1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15\\\\ 2 | 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30\\\\ 3 | 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45\\\\ 4 | 4 8 12 16 20 24 28 32 36 40 44 48 52 56 60\\\\ 5 | 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75\\\\ 6 | 6 12 18 24 30 36 42 48 54 60 66 72 78 84 90\\\\ 7 | 7 14 21 28 35 42 49 56 63 70 77 84 91 98 105\\\\ 8 | 8 16 24 32 40 48 56 64 72 80 88 96 104 112 120\\\\ 9 | 9 18 27 36 45 54 63 72 81 90 99 108 117 126 135\\\\10 | 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150\\\\11 | 11 22 33 44 55 66 77 88 99 110 121 132 143 154 165\\\\12 | 12 24 36 48 60 72 84 96 108 120 132 144 156 168 180\\\\13 | 13 26 39 52 65 78 91 104 117 130 143 156 169 182 195\\\\14 | 14 28 42 56 70 84 98 112 126 140 154 168 182 196 210\\\\15 | 15 30 45 60 75 90 105 120 135 150 165 180 195 210 225,},.{.main_file = "100_for4.zig",.output = "Arrays match!",},.{.main_file = "101_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),},.{.main_file = "102_testing.zig",.output = "",.kind = .@"test",},.{.main_file = "103_tokenization.zig",.output =\\My\\name\\is\\Ozymandias\\King\\of\\Kings\\Look\\on\\my\\Works\\ye\\Mighty\\and\\despair\\This little poem has 15 words!,},.{.main_file = "104_threading.zig",.output =\\Starting work...\\thread 1: started.\\thread 2: started.\\thread 3: started.\\Some weird stuff, after starting the threads.\\thread 2: finished.\\thread 1: finished.\\thread 3: finished.\\Zig is cool!,},.{.main_file = "105_threading2.zig",.output = "PI ≈ 3.14159265",},.{.main_file = "106_files.zig",.output = "Successfully wrote 18 bytes.",},.{.main_file = "107_files2.zig",.output =\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\Successfully Read 18 bytes: It's zigling time!,},.{.main_file = "108_labeled_switch.zig",.output = "The pull request has been merged.",},.{.main_file = "109_vectors.zig",.output =\\Max difference (old fn): 0.014\\Max difference (new fn): 0.014,},.{ .main_file = "110_quiz9.zig", .output =\\Toggle pins with XOR on PORTB\\-----------------------------\\ 1100 // (initial state of PORTB)\\^ 0101 // (bitmask)\\= 1001\\\\ 1100 // (initial state of PORTB)\\^ 0011 // (bitmask)\\= 1111\\\\Set pins with OR on PORTB\\-------------------------\\ 1001 // (initial state of PORTB)\\| 0100 // (bitmask)\\= 1101\\\\ 1001 // (reset state)\\| 0100 // (bitmask)\\= 1101\\\\Clear pins with AND and NOT on PORTB\\------------------------------------\\ 1110 // (initial state of PORTB)\\& 1011 // (bitmask)\\= 1010\\\\ 0111 // (reset state)\\& 1110 // (bitmask)\\= 0110},.{.main_file = "999_the_end.zig",.output =\\\\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.,},};
# ZiglingsWelcome to Ziglings! This project contains a series of tinybroken programs (and one nasty surprise). By fixing them, you'lllearn how to read and write [Zig](https://ziglang.org/) code.Those broken programs need your help! (You'll also save theplanet from evil aliens and help some friendly elephants sticktogether, which is very sweet of you.)This project was initiated by [Dave Gauer](https://ratfactor.com/) and is directly inspiredby the brilliant and fun [rustlings](https://github.com/rust-lang/rustlings) project.Indirect inspiration comes from [Ruby Koans](http://rubykoans.com/) and the Little LISPer/LittleSchemer series of books.## Intended AudienceThis will probably be difficult if you've _never_ programmedbefore. But no specific programming experience is required. Andin particular, you are _not_ expected to have any priorexperience with "systems programming" or a "systems" levellanguage such as C.Each exercise is self-contained and self-explained. However,you're encouraged to also check out these Zig language resourcesfor more detail:* https://ziglang.org/learn/* https://ziglearn.org/* https://ziglang.org/documentation/master/* [Zig in Depth! (video series)](https://www.youtube.com/watch?v=MMtvGA1YhW4&list=PLtB7CL7EG7pCw7Xy1SQC53Gl8pI7aDg9t&pp=iAQB)Also, the [Zig community](https://github.com/ziglang/zig/wiki/Community)is incredibly friendly and helpful!## Getting StartedInstall a [development build](https://ziglang.org/download/) ofthe Zig compiler. (See the "master" section of the downloadspage.)Verify the installation and build number of `zig` like so:```$ zig version0.15.0-dev.xxxx+xxxxxxxxx```Clone this repository with Git:```git clone https://codeberg.org/ziglings/exercises.git ziglingscd ziglings```Then run `zig build` and follow the instructions to begin!```zig build```Note: The output of Ziglings is the unaltered output from the Zigcompiler. Part of the purpose of Ziglings is to acclimate you toreading these.## A Note About Versions**Hint:** To check out Ziglings for a stable release of Zig, you can usethe appropriate tag.The Zig language is under very active development. In order to becurrent, Ziglings tracks **development** builds of the Zigcompiler rather than versioned **release** builds. The laststable release was `0.14.1`, but Ziglings needs a dev build withpre-release version "0.15.0" and a build number at least as highas that shown in the example version check above.It is likely that you'll download a build which is _greater_ thanthe minimum.Once you have a build of the Zig compiler that works withZiglings, they'll continue to work together. But keep in mindthat if you update one, you may need to also update the other.### Version Changes* *2025-08-15* zig 0.15.0-dev.1519 - changes in array list, see [#24801](https://github.com/ziglang/zig/pull/24801)* *2025-08-08* zig 0.15.0-dev.1380 - changes in build system, see [#24588](https://github.com/ziglang/zig/pull/24588)* *2025-07-22* zig 0.15.0-dev.1092 - various changes due to new I/O API, see [#24488](https://github.com/ziglang/zig/pull/24488)* *2024-09-16* zig 0.14.0-dev.1573 - introduction of labeled switch, see [#21257](https://github.com/ziglang/zig/pull/21257)* *2024-09-02* zig 0.14.0-dev.1409 - several changes in std.builtin, see [#21225](https://github.com/ziglang/zig/pull/21225)* *2024-08-04* zig 0.14.0-dev.1224 - several changes in build system, see [#21115](https://github.com/ziglang/zig/pull/21115)* *2024-08-04* zig 0.14.0-dev.839 - several changes in build system, see [#20580](https://github.com/ziglang/zig/pull/20580), [#20600](https://github.com/ziglang/zig/issues/20600)* *2024-06-17* zig 0.14.0-dev.42 - changes in `std.mem.split and tokenize` - see [#15579](https://github.com/ziglang/zig/pull/15579)* *2024-05-29* zig 0.13.0-dev.339 - rework std.Progress - see [#20059](https://github.com/ziglang/zig/pull/20059)* *2024-03-21* zig 0.12.0-dev.3518 - change to @fieldParentPtr - see [#19470](https://github.com/ziglang/zig/pull/19470)* *2024-03-21* zig 0.12.0-dev.3397 - rename std.os to std.posix - see [#5019](https://github.com/ziglang/zig/issues/5019)* *2024-03-14* zig 0.12.0-dev.3302 - changes in `std.fmt` - floating-point formatting implementation - see [#19229](https://github.com/ziglang/zig/pull/19229)* *2024-02-05* zig 0.12.0-dev.2618 - changes in `build system` - from `Step.zig_exe` to `Step.graph.zig_exe` - see [#18778](https://github.com/ziglang/zig/issues/18778)* *2024-01-05* zig 0.12.0-dev.2043 - rename of `std.Build.FileSource` to `std.Build.LazyPath` - see [#16353](https://github.com/ziglang/zig/issues/16353)* *2023-10-24* zig 0.12.0-dev.1243 - changes in `std.ChildProcess`: renamed exec to run - see [#5853](https://github.com/ziglang/zig/issues/5853)* *2023-06-26* zig 0.11.0-dev.4246 - changes in compile step (now it can be null)* *2023-06-26* zig 0.11.0-dev.3853 - removal of destination type from all cast builtins* *2023-06-20* zig 0.11.0-dev.3747 - `@enumToInt` is now `@intFromEnum` and `@intToFloat` is now `@floatFromInt`* *2023-05-25* zig 0.11.0-dev.3295 - `std.debug.TTY` is now `std.io.tty`* *2023-04-30* zig 0.11.0-dev.2704 - use of the new `std.Build.ExecutableOptions.link_libc` field* *2023-04-12* zig 0.11.0-dev.2560 - changes in `std.Build` - remove run() and install()* *2023-04-07* zig 0.11.0-dev.2401 - fixes of the new build system - see [#212](https://github.com/ratfactor/ziglings/pull/212)* *2023-02-21* zig 0.11.0-dev.2157 - changes in `build system` - new: parallel processing of the build steps* *2023-02-21* zig 0.11.0-dev.1711 - changes in `for loops` - new: Multi-Object For-Loops + Struct-of-Arrays* *2023-02-12* zig 0.11.0-dev.1638 - changes in `std.Build` cache_root now returns a directory struct* *2023-02-04* zig 0.11.0-dev.1568 - changes in `std.Build` (combine `std.build` and `std.build.Builder` into `std.Build`)* *2023-01-14* zig 0.11.0-dev.1302 - changes in `@addWithOverflow` (now returns a tuple) and `@typeInfo`; temporary disabled async functionality* *2022-09-09* zig 0.10.0-dev.3978 - change in `NativeTargetInfo.detect` in build* *2022-09-06* zig 0.10.0-dev.3880 - Ex 074 correctly fails again: comptime array len* *2022-08-29* zig 0.10.0-dev.3685 - `@typeName()` output change, stage1 req. for async* *2022-07-31* zig 0.10.0-dev.3385 - std lib string `fmt()` option changes* *2022-03-19* zig 0.10.0-dev.1427 - method for getting sentinel of type changed* *2021-12-20* zig 0.9.0-dev.2025 - `c_void` is now `anyopaque`* *2021-06-14* zig 0.9.0-dev.137 - std.build.Id `.Custom` is now `.custom`* *2021-04-21* zig 0.8.0-dev.1983 - std.fmt.format() `any` format string required* *2021-02-12* zig 0.8.0-dev.1065 - std.fmt.format() `s` (string) format string required## Advanced UsageIt can be handy to check just a single exercise:```zig build -Dn=19```Or run all exercises, starting from a specific one:```zig build -Ds=27```Or let Ziglings pick an exercise for you:```zig build -Drandom```You can also run without checking for correctness:```zig build -Dn=19 test```Or skip the build system entirely and interact directly with thecompiler if you're into that sort of thing:```zig run exercises/001_hello.zig```Calling all wizards: To prepare an executable for debugging,install it to zig-cache/bin with:```zig build -Dn=19 install```To get a list of all possible options, run:```zig build -Dn=19 -linstall Install 019_functions2.zig to prefix pathuninstall Uninstall 019_functions2.zig from prefix pathtest Run 019_functions2.zig without checking output...```To reset the progress (have it run all the exercises that have already been completed):```zig build -Dreset```## What's CoveredThe primary goal for Ziglings is to cover the core Zig language.It would be nice to cover the Standard Library as well, but thisis currently challenging because the stdlib is evolving evenfaster than the core language (and that's saying something!).Not only would stdlib coverage change very rapidly, someexercises might even cease to be relevant entirely.Having said that, there are some stdlib features that areprobably here to stay or are so important to understand that theyare worth the extra effort to keep current.Conspicuously absent from Ziglings are a lot of stringmanipulation exercises. This is because Zig itself largely avoidsdealing with strings. Hopefully there will be an obvious way toaddress this in the future. The Ziglings crew loves strings!Zig Core Language* [x] Hello world (main needs to be public)* [x] Importing standard library* [x] Assignment* [x] Arrays* [x] Strings* [x] If* [x] While* [x] For* [x] Functions* [x] Errors (error/try/catch/if-else-err)* [x] Defer (and errdefer)* [x] Switch* [x] Unreachable* [x] Enums* [x] Structs* [x] Pointers* [x] Optionals* [x] Struct methods* [x] Slices* [x] Many-item pointers* [x] Unions* [x] Numeric types (integers, floats)* [x] Labelled blocks and loops* [x] Loops as expressions* [x] Builtins* [x] Inline loops* [x] Comptime* [x] Sentinel termination* [x] Quoted identifiers @""* [x] Anonymous structs/tuples/lists* [ ] Async <--- ironically awaiting upstream Zig updates* [X] Interfaces* [X] Bit manipulation* [X] Working with C* [ ] Opaque types (anyopaque)* [X] Threading* [x] Labeled switch* [x] Vector operations (SIMD)Zig Standard Library* [X] String formatting* [X] Testing* [X] Tokenization* [X] File handling## ContributingContributions are very welcome! I'm writing this to teach myselfand to create the learning resource I wished for. There will betons of room for improvement:* Wording of explanations* Idiomatic usage of Zig* Additional exercisesPlease see [CONTRIBUTING](https://codeberg.org/ziglings/exercises/src/branch/main/CONTRIBUTING.md)in this repo for the full details.
MIT LicenseCopyright (c) 2021 Dave Gauer, Chris BoeschPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE.
# ContributingBy reading this document, you have already entered the Elite Hallof Ziglings Maintenance!## The Ziglings AudienceZiglings is intended for programmers of all experience levels. Nospecific language knowledge is expected. Anyone who can installthe current Zig snapshot, setup a copy of Ziglings, and knowscommon language building blocks (if/then/else, loops, andfunctions) is ready for Ziglings.Ziglings is intended to be completely self-contained. If youcan't solve an exercise from the information you've gleaned sofar from Ziglings, then the exercise probably needs someadditional work. Please file an issue!If an example doesn't match a description or if something isunclear, please file an issue!## Spelling/GrammarIf you see any typos, please file an issue...or make a pullrequest!No mistake is too small. The Ziglings must be perfect. :-)## IdeasIf you have ideas for new lessons or a way Ziglings could beimproved, don't hesitate to file an issue.Feel free to submit new exercises but please understand that theymay be heavily edited or rejected entirely if we feel they don'tfit for one reason or another.## Platforms and Zig VersionsBecause it uses the Zig build system, Ziglings should workwherever Zig does.Since Ziglings is a Zig language learning resource, it tracks thecurrent development snapshots of Zig from the official websitedownloads page.If you run into an error in Ziglings caused by breaking changesin the latest development build of Zig, that's a new bug inZiglings. Please file an issue...or make a pull request!## FormattingAll exercises should conform to `zig fmt`.## Pull Request WorkflowZiglings uses the "standard" Codeberg workflow as guided by the Webinterface. Specifically:* Fork this repository* Create a branch from `main` for your work:`git checkout -b my-branch`* Make changes, commit them* When your changes are ready for review, push your branch:`git push origin my-branch`* Create a pull request from your branch to `ziglings/main`* Your faithful Ziglings maintainers will take a look at yourrequest ASAP (we don't talk about May-July, LOL)* Once the changes are reviewed, your request will be merged andeternal Ziglings contributor glory is yours!## LicenceIf you submit your contribution to the repository/project,you agree that your contribution will be licensed underthe license of this repository/this project.Please note, it does not change your rights to use your owncontribution for any other purpose.## The SecretsIf you want to peek at the secrets, take a look at the `patches/`directory.
.git.DS_Store/.zig-cache//zig-out//answers//patches/healed//output/.progress.txt# Leave this in here for older zig versions/zig-cache/.gitattributes.gitea.gitea/issue_template.md.gitignore.woodpecker.woodpecker/eowyn.yamlCNAME