ziglings – all exercises solved (v0.16.0)

Exe: 001_hello.zig


//
// Oh no, this is supposed to print "Hello world!" but it needs
// your help.
//
// Zig functions are private by default, but the main() function
// must 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");

pub fn main() void {
    std.debug.print("Hello world!n", .{});
}

Exe: 002_std.zig


//
// 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:
//

const std = @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

Exe: 003_assignment.zig


//
// 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 {
    var n: u8 = 50;
    n = n + 5;

    const pi: u32 = 314159;

    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
    // 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 });
}

Exe: 004_arrays.zig


//
// 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?
    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):
    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[3];

    // (Problem 3)
    // Use the len property to get the length of the array:
    const length = some_primes.len;

    std.debug.print("First: {}, Fourth: {}, Length: {}n", .{
        first, fourth, length,
    });
}

Exe: 005_arrays2.zig


//
// 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 7
    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{ 1, 0, 0, 1 } ** 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", .{});
}

Exe: 006_strings.zig


//
// 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[4];

    // (Problem 2)
    // Use the array repeat '**' operator to make "ha ha 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;

    // 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!
    //
}

Exe: 007_strings2.zig


//
// 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 guitar
        \Jamming good with Andrew Kelley
        \And the Spiders from Mars
    ;

    std.debug.print("{s}n", .{lyrics});
}

Exe: 008_quiz.zig


//
// 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.
    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.
    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[1] = letters[x];

    x = 5;
    lang[2] = letters[x];

    // We want to "Program in Zig!" of course:
    std.debug.print("Program in {s}!n", .{lang});
}

Exe: 009_if.zig


//
// 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 = 42;

    // Please fix this condition:
    if (foo == 42) {
        // We want our program to print this message!
        std.debug.print("Foo is 42!n", .{});
    } else {
        std.debug.print("Foo is not 42!n", .{});
    }
}

Exe: 010_if2.zig


//
// 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 (discount) 17 else 20;

    std.debug.print("With the discount, the price is ${}.n", .{price});
}

Exe: 011_while.zig


//
// 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 (n < 1024) {
        // Print the current number
        std.debug.print("{} ", .{n});

        // Set n to n multiplied by 2
        n *= 2;
    }

    // Once the above is correct, this will print "n=1024"
    std.debug.print("n={}n", .{n});
}

Exe: 012_while2.zig


//
// 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 < 1024) : (n *= 2) {
        // Print the current number
        std.debug.print("{} ", .{n});
    }

    // As in the last exercise, we want this to result in "n=1024"
    std.debug.print("n={}n", .{n});
}

Exe: 013_while3.zig


//
// 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 single time the loop restarts,
// even when a `continue` statement skips the rest of the loop body.
//
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) continue;
        if (n % 5 == 0) continue;
        std.debug.print("{} ", .{n});
    }

    std.debug.print("n", .{});
}

Exe: 014_while4.zig


//
// 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 (n == 4) break;
    }

    // Result: we want n=4
    std.debug.print("n={}n", .{n});
}

Exe: 015_for.zig


//
// 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 (story) |scene| {
        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.

Exe: 016_for2.zig


//
// 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, 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.
        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!

Exe: 017_quiz2.zig


//
// 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("std");

pub fn main() void {
    var i: u8 = 1;
    const stop_at: u8 = 16;

    // What kind of loop is this? A 'for' or a 'while'?
    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("{}", .{i});
        }
        std.debug.print(", ", .{});
    }
    std.debug.print("n", .{});
}

Exe: 018_functions.zig


//
// 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?
//
fn deepThought() u8 {
    return 42; // Number courtesy Douglas Adams
}

Exe: 019_functions2.zig


//
// 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) void {
//         ...
//     }
//
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(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
    // returns "a to the power of b" as that same numeric type.
}

Exe: 020_quiz3.zig


//
// 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) void {
    for (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) u16 {
    var n: u16 = 0;
    var total: u16 = 1;

    while (n < number) : (n += 1) {
        total *= 2;
    }

    return total;
}

Exe: 021_errors.zig


//
// 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,
    TooSmall,
    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: MyNumberError = numberFail(n);

        if (number_error == MyNumberError.TooBig) {
            std.debug.print(">4. ", .{});
        }
        if (number_error == MyNumberError.TooSmall) {
            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;
}

Exe: 022_errors2.zig


//
// 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: MyNumberError!i8 = 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", .{});
}

Exe: 023_errors3.zig


//
// 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) 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) MyNumberError!u32 {
    if (n < 5) {
        return MyNumberError.TooSmall;
    } else {
        return n + 20;
    }
}

Exe: 024_errors4.zig


//
// 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) catch |err| {
        if (err == MyNumberError.TooSmall) {
            return 10;
        }

        return err;
    };
}

fn detectProblems(n: u32) MyNumberError!u32 {
    if (n < 10) return MyNumberError.TooSmall;
    if (n > 20) return MyNumberError.TooBig;
    return n;
}

Exe: 025_errors5.zig


//
// 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 = try 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;
}

Exe: 026_hello2.zig


//
// 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(init: std.process.Init) !void {
    // Instance for input/output operations; we will learn more about this later.
    const io = init.io;

    // We get a Writer for Standard Out...
    var stdout_writer = std.Io.File.stdout().writer(io, &.{});
    // ...and extract its interface so we can print() to it.
    const stdout = &stdout_writer.interface;

    // 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.
    try stdout.print("Hello world!n", .{});
}

Exe: 027_defer.zig


//
// 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 Twon":
    defer std.debug.print("Twon", .{});
    std.debug.print("One ", .{});
}

Exe: 028_defer2.zig


//
// 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("(", .{});

    defer 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", .{});
}

Exe: 029_errdefer.zig


//
// 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:
    errdefer 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;
}

Exe: 030_switch.zig


//
// 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.
            else => {
                std.debug.print("?", .{});
            },
        }
    }

    std.debug.print("n", .{});
}

Exe: 031_switch2.zig


//
// 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 '!'.
            else => '!',
        };

        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", .{});
}

Exe: 032_unreachable.zig


//
// 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;
            },
            else => unreachable,
        }

        std.debug.print("{} ", .{current_value});
    }

    std.debug.print("n", .{});
}

Exe: 033_iferror.zig


//
// 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. "
            MyNumberError.TooSmall => std.debug.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;
}

Exe: 034_quiz4.zig


//
// 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(init: std.process.Init) !void {
    const io = init.io;
    var stdout_writer = std.Io.File.stdout().writer(io, &.{});
    const stdout = &stdout_writer.interface;

    const my_num: u32 = try getNumber();

    try stdout.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;
}

Exe: 035_enums.zig


//
// 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 { inc, pow, dec };

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", .{});
}

Exe: 036_enums2.zig


//
// 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 = 0x0000FF,
};

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: #{x:0>6}">Blue</span>
        \</p>
        \
    , .{
        @intFromEnum(Color.red),
        @intFromEnum(Color.green),
        @intFromEnum(Color.blue), // Oops! We're missing something!
    });
}

Exe: 037_structs.zig


//
// 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,
    health: u32,
};

pub fn main() void {
    // Please initialize Glorp with 100 health.
    var glorp_the_wise = Character{
        .role = Role.wizard,
        .gold = 20,
        .experience = 10,
        .health = 100,
    };

    // 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,
    });
}

Exe: 038_structs2.zig


//
// 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 Wise
    chars[0] = Character{
        .role = Role.wizard,
        .gold = 20,
        .health = 100,
        .experience = 10,
    };

    chars[1] = Character{
        .role = Role.bard,
        .gold = 10,
        .health = 100,
        .experience = 20,
    };

    // 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.

Exe: 039_pointers.zig


//
// 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 = num1_pointer.*;

    std.debug.print("num1: {}, num2: {}n", .{ num1, num2 });
}

Exe: 040_pointers2.zig


//
// 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: *const u8 = &a; // fix this!

    std.debug.print("a: {}, b: {}n", .{ a, b.* });
}

Exe: 041_pointers3.zig


//
// 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!
    var p: *u8 = undefined;

    p = &foo;
    p.* += 1;
    p = &bar;
    p.* += 1;
    std.debug.print("foo={}, bar={}n", .{ foo, bar });
}

Exe: 042_pointers4.zig


//
// 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 {
    x.* = 5; // fix me!
}

Exe: 043_pointers5.zig


//
// 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 values
    experience: 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(&glorp);
}

// 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);
    }
}

Exe: 044_quiz5.zig


//
//    "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 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);

    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;
    }
}

Exe: 045_optionals.zig


//
// 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 orelse 42;

    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;
//

Exe: 046_optionals2.zig


//
// 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 orelse return;
    }
}

Exe: 047_methods.zig


//
// 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! ***
            heat_ray.zap(&alien.*);

            // 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", .{});
}

Exe: 048_methods2.zig


//
// 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]isited
        const 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.tail.? 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

Exe: 049_quiz6.zig


//
//    "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 getTrunk(self: *Elephant) *Elephant {
        return self.trunk.?;
    }

    pub fn hasTrunk(self: *Elephant) bool {
        return (self.trunk != null);
    }

    // ---------------------------------------------------

    pub fn visit(self: *Elephant) void {
        self.visited = true;
    }

    pub fn print(self: *Elephant) void {
        // Prints elephant letter and [v]isited
        const 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;
        }
    }
}

Exe: 050_no_value.zig


//
//    "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 variable may be set to undefined, but attempting to
//       read its value before assigning one 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 you have assigned foo to have _no value_!
//
// * 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 of "no value", you have an error.
//       The example error union type of MyError!u8 means that foo
//       either holds a u8 value OR a MyError 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 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 = undefined;
    first_line1 = "That is not dead";

    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.
    std.debug.print("{s} {!s} / ", .{ first_line1, first_line2 });

    printSecondLine();
}

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.?});
}

Exe: 051_values.zig


//
// 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.

var 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 = std.debug.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!

Exe: 052_slices.zig


//
// 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[0..4];
    const hand2: []u8 = cards[4..];

    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: []u8) 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.

Exe: 053_slices2.zig


//
// 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: []const u8 = scrambled[15..23];
    const base2: []const u8 = scrambled[6..10];
    const base3: []const u8 = scrambled[32..];
    printPhrase(base1, base2, base3);

    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: []const u8, part2: []const u8, part3: []const u8) void {
    std.debug.print("'{s} {s} {s}.' ", .{ part1, part2, part3 });
}

Exe: 054_manypointers.zig


//
// 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 = zen_manyptr[0..zen12.len];

    // 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                      |
//   +---------------+----------------------------------------------+

Exe: 055_unions.zig


//
// 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.a);
    printInsect(bee, AntOrBee.b);

    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}),
    }
}

Exe: 056_unions2.zig


//
// 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(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}),
    }
}

// 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 }

Exe: 057_unions3.zig


//
// 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 InsectStat = enum {
//     flowers_visited,
//     still_alive,
// };

const Insect = union(enum) {
    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"!

Exe: 058_quiz7.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}", .{self.place.name}),
            .path => print("--{}->", .{self.path.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 entry
        return &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 Point
    const 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!)

Exe: 059_integers.zig


//
// 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{
        0o132, // octal
        0b1101001, // binary
        0x67, // hex
    };

    print("{s} is cool.n", .{zig});
}

Exe: 060_floats.zig


//
// 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: f32 = 0.453592 * 4480e3;

    // By default, float values are formatted in standard decimal
    // notation. Experiment with '{d}' and '{d:.3}' to see how
    // decimal formatting works, or try '{e}' and '{e:.3}' for
    // scientific notation.
    // NOTE: The weight of the shuttle is a huge number, a scientific notation
    // may be more appropriate.
    print("Shuttle liftoff weight: {e:.3} metric tonsn", .{shuttle_weight / 1e3});
}

// 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.

Exe: 061_coercions.zig


//
// 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: ?*[1]u8 = &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]});
}

Exe: 062_loop_expressions.zig


//
// 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;
    } else null;

    if (current_lang) |cl| {
        print("Current language: {s}n", .{cl});
    } else {
        print("Did not find a three-letter language name. :-(n", .{});
    }
}

Exe: 063_labels.zig


//
// 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 food;
    } else menu[0];

    // ^ 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!

Exe: 064_builtins.zig


//
// 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 = 0b00010010;
    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);
    print("{b:0>8} backwards is {b:0>8}.n", .{ input, tupni });
}

Exe: 065_builtins2.zig


//
// 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.myself = &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 Johnston

    print("He has room in his heart for:", .{});

    // A StructFields array
    const 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].type != void) {
        print(" {s}", .{fields[0].name});
    }

    if (fields[1].type != void) {
        print(" {s}", .{fields[1].name});
    }

    if (fields[2].type != 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.
// After Zig 0.10.0, `@typeName` began prefixing the returned type name
// with the source file name. For example, "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 just after the ".")
//
// 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: type) []const u8 {
    const find = @import("std").mem.find;

    // Turn "065_builtins2.Narcissus" into "Narcissus"
    const name = @typeName(myType);
    return name[find(u8, name, ".").? + 1 ..];
}

Exe: 066_comptime.zig


//
// "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: 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.
    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),
    });
}

Exe: 067_comptime2.zig


//
// 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        *    |      ..    .
//     *    |       *  .        .    .   .    --*--  .     *  .
//  .     .    .    .   . . .      .        .   |   .    .  .
// (ASCII art depicting a starry sky with "comptime" as rising star)
//
// 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...
    //
    comptime 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);
}

Exe: 068_comptime3.zig


//
// 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.Io.Writer.print() 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, scale: u32) void {
        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) my_scale = 1; //@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 scale

    minnow.scaleMe(scale);
    minnow.printMe();

    scale -= 16; // 1:16 scale

    shark.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.

Exe: 069_comptime4.zig


//
// 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]u8
    const s2 = makeSequence(u32, 5); // creates a [5]u32
    const s3 = makeSequence(i64, 7); // creates a [7]i64

    print("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, comptime size: usize) [size]T {
    var sequence: [size]T = undefined;
    var i: usize = 0;

    while (i < size) : (i += 1) {
        sequence[i] = @as(T, @intCast(i)) + 1;
    }

    return sequence;
}

Exe: 070_comptime5.zig


//
// 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 = @hasDecl(MyType, "waddle");
    const quacks_like_duck = @hasDecl(MyType, "quack");

    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;
}

Exe: 071_comptime6.zig


//
// 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;

    inline for (fields) |field| {
        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", .{});
}

Exe: 072_comptime7.zig


//
// 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":
    inline while (i < instructions.len) : (i += 3) {

        // 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});
}

Exe: 073_comptime8.zig


//
// 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(4);

    print("My llama value is {}.n", .{my_llama});
}

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!
    //
    // 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!

Exe: 074_comptime9.zig


const print = @import("std").debug.print;

// We're going to (ab)use the power of Zig to make animal hybrid creatures!
// What do you think a GatorMouse would look like?  Eek.
//
// Let's try a MouseLlama instead.
//
// We'll make a function that runs at comptime and takes a short code describing
// the desired creature. A Mouse is represented by "m" and a Llama is "lm".
// A MouseLlama hybrid, then, would be represented by "mlm".

const Animal = enum {
    Mouse,
    Llama,
    Gator,
};

// makeCreature takes the count of animals making up the hybrid creature (so we
// know how big a pen we'll need) and a format string, like the "mlm" for
// MouseLlama.
fn makeCreature(comptime count: usize, comptime fmt: []const u8) [count]Animal {

    // Since not every animal is represented by a single character, we need to
    // track the state of things as we move along. For example, if we see an
    // "m", is that a new Mouse or the end of a Llama?
    const State = enum {
        start, // Ready to start a new animal.
        l, // This means we've seen an "l", so if we see an "m", we know it's a Llama.
    };
    comptime var state = State.start;
    comptime var next_animal: usize = 0;

    // We return an array of animals representing the creature. (This is why we
    // really needed the 'count' parameter. Arrays need a size.)
    var animals: [count]Animal = .{undefined} ** count;

    inline for (fmt) |char| {

        // This is a good spot to add a @compileLog() call if you need to debug
        // any variables... (Come back here after you see main().)

        switch (state) {
            .start => switch (char) {
                // We've seen the start of a Llama.
                'l' => state = .l,

                // Mice are smaller.  An "m" is a full Mouse.
                'm' => {
                    animals[next_animal] = .Mouse;
                    next_animal += 1;
                },

                // @compileError lets us stop the build immediately if something
                // is wrong. It's like @compileLog but it prints a message
                // instead of inspecting values.
                //
                // What do you think happens with Gators? Do they join with
                // other animals or is this an error?
                'g' => {
                    animals[next_animal] = .Gator;
                    next_animal += 1;
                },

                else => @compileError("No animal starts with '" ++ char ++ "'!"),
            },

            .l => switch (char) {
                // We've seen the end of a Llama.
                'm' => {
                    animals[next_animal] = .Llama;
                    next_animal += 1;
                    // Something is missing here. After we finish a Llama, we
                    // need to be ready to _start_ over with a new animal...
                    state = .start;
                },

                else => @compileError("Only llamas start with 'l'!"),
            },
        }
    }

    if (state != .start) {
        @compileError("Oh no, an incomplete llama!");
    }
    if (next_animal != count) {
        @compileError("Creature is missing an animal (format string too short).");
    }

    return animals;
}

pub fn main() void {
    // Once you've fixed the ??? marks above, this makeCreature call will still
    // only succeed if you move it outside of main, so it will run at comptime.
    //
    // With the call here, Zig will try to make the creature at runtime, and
    // you'll get an interesting error.
    //
    // You may think the state got mixed up, but if you use @compileLog to check
    // some variables in makeCreature, you'll see that Zig is trying to compare
    // comptime values with "[runtime value]", which will never match.
    //
    // You can solve this by adding "comptime" to two of the variables in
    // makeCreature...
    const creature = makeCreature(2, "mlm");

    for (creature) |animal| {
        // @tagName gives us a string representing which variant of an enum we
        // have. This lets us print the names of animals without repeating them
        // here.
        print("{s}", .{@tagName(animal)});
    }
    print(" joins the crew!", .{});
}

Exe: 075_quiz8.zig


//
// 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 {
    return Path{
        .from = from,
        .to = to,
        .dist = dist,
    };
}

// 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 Point
    const 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", .{});
}

Exe: 076_sentinels.zig


//
// 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 (my_seq) |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_seq[i] != my_sentinel) {
                print("{}", .{my_seq[i]});
                i += 1;
            }
        },
        else => unreachable,
    }
    print(". ", .{});
}

Exe: 077_sentinels2.zig


//
// ------------------------------------------------------------
//  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 = foo.data[0..foo.length];

    print("{s}n", .{printable});
}

Exe: 078_sentinels3.zig


//
// 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 = @ptrCast(data);

    print("{s}n", .{printable});
}

Exe: 079_quoted_identifiers.zig


//
// 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",
    });
}

Exe: 080_anonymous_structs.zig


//
// 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 = Circle(i32){
        .center_x = 25,
        .center_y = 70,
        .radius = 15,
    };

    const circle2 = Circle(f32){
        .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.

Exe: 081_anonymous_structs2.zig


//
// 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(circle: anytype) void {
    print("x:{} y:{} radius:{}n", .{
        circle.center_x,
        circle.center_y,
        circle.radius,
    });
}

Exe: 082_anonymous_structs3.zig


//
// 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 = @typeInfo(@TypeOf(tuple)).@"struct".fields;

    // 2. Loop through each field. This must be done at compile
    // time.
    //
    //     Hint: remember 'inline' loops?
    //
    inline 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):true
        //
        // Hint: Be careful! If your 'lhs' is a type, @field() looks
        // for declarations. If it's a value, it looks for data.
        //
        print(""{s}"({any}):{any} ", .{
            field.name,
            field.type,
            @field(tuple, field.name),
        });
    }
}

Exe: 083_anonymous_lists.zig


//
// 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: [5]u8 = .{ 'h', 'e', 'l', 'l', 'o' };
    print("I say {s}!n", .{hello});
}

Exe: 084_interfaces.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("=== Doctor Zoraptera's Insect Report ===n", .{});
    for (my_insects) |insect| {
        // Almost done! We want to print() each insect with a
        // single method call here.
        insect.print();
    }
}

// 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.

Exe: 085_async.zig


//
// In previous versions of Zig, async/await used special keywords
// like 'suspend', 'resume', and 'async' that operated on stackframes
// directly. Those keywords no longer exist!
//
// Zig 0.16 replaced them with a unified I/O interface: std.Io.
// This interface uses a VTable pattern - a struct of function pointers -
// to abstract over different concurrency backends:
//
//   * Threaded  - thread-pool based I/O
//   * Evented   - chooses the best event-loop backend for your OS:
//       * Uring    on Linux (io_uring)
//       * Kqueue   on BSD/macOS
//       * Dispatch on macOS (Grand Central Dispatch)
//
// The Io struct itself is tiny:
//
//     const Io = struct {
//         userdata: ?*anyopaque,   // opaque state of the backend
//         vtable: *const VTable,   // table of function pointers
//     };
//
// Your code receives an Io value and calls methods on it.
// The backend is chosen at initialization time - your code doesn't
// need to know which one it is!
//
// In Zig 0.16, main() receives a std.process.Init struct to opt
// into I/O and concurrency support:
//
//     pub fn main(init: std.process.Init) !void {
//         const io = init.io;
//         // ... use io ...
//     }
//
// Let's start simple. Fix the main function to extract the Io
// interface from init, then use it to get the current time.
//
const std = @import("std");

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    // Get the current wall-clock time using the Io interface.
    // Hint: Timestamp.now() takes an Io and a Clock type (.real = wall clock).
    const timestamp = std.Io.Timestamp.now(io, .real);

    // Print the timestamp in seconds since the Unix epoch.
    std.debug.print("Current time: {}s since epochn", .{timestamp.toSeconds()});
}

Exe: 086_async2.zig


//
// Now that we know how to get an Io value, let's use it for
// asynchronous execution!
//
// io.async() launches a function and returns a Future. The result
// won't necessarily be available until you call .await() on it:
//
//     var future = io.async(someFunction, .{ arg1, arg2 });
//     const result = future.await(io);
//
// The function *may* run immediately or on another thread -
// your code doesn't need to care! That's the beauty of the
// Io abstraction.
//
// IMPORTANT: Every Future MUST be either .await()ed or .cancel()ed.
// Failing to do so leaks resources! A safe pattern is:
//
//     var future = io.async(myFn, .{});
//     defer _ = future.cancel(io);  // safety net
//     // ... later, if we want the result:
//     const result = future.await(io);
//     // (await after cancel is fine — it just returns the result)
//
// Both .await() and .cancel() block until the task finishes and
// return the result. The difference is that .cancel() also
// requests the task to stop at its next cancellation point.
// Calling either one more than once is safe — subsequent calls
// just return a copy of the result.
//
// Fix this program so that computeAnswer runs asynchronously
// and its result is properly awaited.
//
const std = @import("std");
const print = std.debug.print;

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    // Launch computeAnswer asynchronously.
    var future = io.async(computeAnswer, .{ 6, 7 });
    defer _ = future.cancel(io); // always clean up!

    print("Computing... ", .{});

    // Now collect the result. What method on Future gives us
    // the value, blocking until it's ready?
    const answer = future.await(io);

    print("The answer is: {}n", .{answer});
}

fn computeAnswer(a: u32, b: u32) u32 {
    return a * b;
}

Exe: 087_async3.zig


//
// The real power of async shows when you launch MULTIPLE tasks!
//
// With io.async(), you can start several operations, then await
// them all. The Io backend may run them concurrently:
//
//     var f1 = io.async(taskA, .{});
//     defer _ = f1.cancel(io);
//     var f2 = io.async(taskB, .{});
//     defer _ = f2.cancel(io);
//     const a = f1.await(io);
//     const b = f2.await(io);
//
// Notice the defer pattern: each async call is immediately
// followed by a defer cancel. This ensures cleanup even if
// we return early or hit an error before reaching await.
// Since await/cancel are idempotent, the defer is harmless
// if we've already awaited.
//
// Fix this program to launch both tasks and collect their results.
//
const std = @import("std");
const print = std.debug.print;

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    // Launch both tasks asynchronously.
    var future_a = io.async(slowAdd, .{ 1, 2 });
    defer _ = future_a.cancel(io);
    var future_b = io.async(slowMul, .{ 6, 7 });
    defer _ = future_b.cancel(io);

    // Await both results.
    const sum = future_a.await(io);
    const product = future_b.await(io);

    print("{} + {} = {}n", .{ 1, 2, sum });
    print("{} * {} = {}n", .{ 6, 7, product });
    print("Total: {}n", .{sum + product});
}

fn slowAdd(a: u32, b: u32) u32 {
    return a + b;
}

fn slowMul(a: u32, b: u32) u32 {
    return a * b;
}

Exe: 088_async4.zig


//
// When you have many tasks that don't return individual values,
// use a Group! A Group is an unordered set of tasks that can
// only be awaited or canceled as a whole:
//
//     var group: std.Io.Group = .init;
//     group.async(io, myTask, .{arg1});
//     group.async(io, myTask, .{arg2});
//     try group.await(io);  // blocks until ALL tasks finish
//
// Important rules:
//   * The return type of functions spawned in a group must be
//     coercible to Cancelable!void (i.e. void, or error{Canceled}!void).
//   * Once you call group.async(), you MUST eventually call
//     group.await() or group.cancel() to release resources.
//   * group.cancel() requests cancellation on ALL members,
//     then blocks until they all finish.
//
// Unlike Future, Group tasks don't return values to the caller.
// They're ideal for parallel work that communicates through
// shared state or side effects (like printing).
//
// Fix this program to await all tasks in the group.
//
const std = @import("std");
const print = std.debug.print;

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    var group: std.Io.Group = .init;

    // Spawn 3 tasks in any order. Each sleeps for (id * 1) seconds
    // before printing, so the output order is deterministic.
    group.async(io, doWork, .{ io, 1 });
    group.async(io, doWork, .{ io, 3 });
    group.async(io, doWork, .{ io, 2 });

    // Wait for all tasks to finish.
    // What Group method blocks until all tasks complete?
    try group.await(io);

    print("All tasks finished!n", .{});
}

fn doWork(io: std.Io, id: u32) void {
    // Sleep ensures deterministic output order.
    io.sleep(std.Io.Duration.fromSeconds(id), .awake) catch return;
    print("Task {} done.n", .{id});
}

Exe: 089_async5.zig


//
// One of the most important features of the new Io system is
// structured cancellation!
//
// Every Future has a .cancel() method that:
//   1. Requests the task to stop (via error.Canceled at the
//      next "cancellation point")
//   2. BLOCKS until the task actually finishes
//   3. Returns whatever result the task produced
//
// A "cancellation point" is any Io function that can return
// error.Canceled - most commonly io.sleep():
//
//     fn myTask(io: std.Io) u32 {
//         io.sleep(...) catch |err| switch (err) {
//             error.Canceled => return 0,  // error handle
//         };
//         return 42;
//     }
//
// This is fundamentally different from killing a thread -
// the task gets a chance to clean up and return a value!
//
// Remember: both .await() and .cancel() block and return the
// result. The only difference is that .cancel() also sends
// the cancellation request. And both are idempotent — calling
// either one again just returns the same result.
//
// Fix this program: the slow task would take 10 seconds,
// but we cancel it after 1 second. The task should detect
// the cancellation and return early.
//
const std = @import("std");
const print = std.debug.print;

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    var future = io.async(slowTask, .{io});
    defer _ = future.cancel(io); // safety net

    // Wait 1 second, then cancel instead of waiting the full 10.
    io.sleep(std.Io.Duration.fromSeconds(1), .awake) catch {};

    print("Canceling slow task...n", .{});

    // We don't want to wait 10 seconds!
    // Which Future method requests cancellation AND returns the result?
    const result = future.cancel(io);

    print("Task returned: {}n", .{result});
}

fn slowTask(io: std.Io) u32 {
    print("Starting long computation...n", .{});

    // Try to sleep for 10 seconds - but we might get canceled!
    io.sleep(std.Io.Duration.fromSeconds(10), .awake) catch |err| switch (err) {
        error.Canceled => {
            print("Task was canceled, cleaning up.n", .{});
            return 0;
        },
    };

    print("Task completed normally.n", .{});
    return 42;
}

Exe: 090_async6.zig


//
// Sometimes you want to race multiple tasks and act on whichever
// finishes first. That's what Select is for!
//
// Select is like a Group, but lets you receive individual results
// as tasks complete — one at a time:
//
//     const Race = std.Io.Select(union(enum) {
//         fast: u32,
//         slow: u32,
//     });
//
//     var buffer: [2]Race.Union = undefined;
//     var sel = Race.init(io, &buffer);
//
//     sel.async(.fast, fastFn, .{io});
//     sel.async(.slow, slowFn, .{io});
//
//     const winner = try sel.await();  // returns first completed
//     switch (winner) {
//         .fast => |val| ...,
//         .slow => |val| ...,
//     }
//     sel.cancelDiscard();  // cancel remaining, discard results
//
// As with all async primitives: tasks spawned in a Select MUST
// be cleaned up. Use sel.cancel() to get remaining results one
// by one (for resource cleanup), or sel.cancelDiscard() if you
// don't need them.
//
// The buffer must be large enough for all tasks that might
// complete before you call cancelDiscard().
//
// Fix this program to receive the winner of the race.
//
const std = @import("std");
const print = std.debug.print;

const RaceResult = std.Io.Select(union(enum) {
    hare: []const u8,
    tortoise: []const u8,
});

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    var buffer: [2]RaceResult.Union = undefined;
    var sel = RaceResult.init(io, &buffer);

    sel.async(.hare, runHare, .{io});
    sel.async(.tortoise, runTortoise, .{io});

    // Wait for the first finisher.
    // What Select method returns the first completed result?
    const winner = try sel.await();

    switch (winner) {
        .hare => |msg| print("Hare: {s}n", .{msg}),
        .tortoise => |msg| print("Tortoise: {s}n", .{msg}),
    }

    // Clean up the loser — we don't need their result.
    sel.cancelDiscard();
}

fn runHare(io: std.Io) []const u8 {
    // The hare is fast — only 1 second!
    io.sleep(std.Io.Duration.fromSeconds(1), .awake) catch return "I got canceled!";
    return "I'm fast!";
}

fn runTortoise(io: std.Io) []const u8 {
    // The tortoise is slow — 10 seconds.
    io.sleep(std.Io.Duration.fromSeconds(10), .awake) catch return "I got canceled!";
    return "Slow and steady...";
}

Exe: 091_async7.zig


//
// When multiple async tasks access shared data, you need
// synchronization! Io provides a Mutex for this:
//
//     var mutex: std.Io.Mutex = .init;
//
//     // In a task:
//     try mutex.lock(io);       // blocks until lock is acquired
//     defer mutex.unlock();
//     // ... critical section: safe to modify shared data ...
//
// Without the mutex, concurrent tasks could read and write the
// same memory simultaneously, causing a data race — the result
// would be unpredictable.
//
// mutex.lock() is a cancellation point — it can return
// error.Canceled. There's also tryLock() which returns
// immediately (true if acquired, false if not).
//
// Fix this program so the counter is correctly synchronized.
// Without the fix, the final count would be unpredictable.
// With it, four tasks incrementing 100 times each = 400.
//
const std = @import("std");
const print = std.debug.print;

const SharedState = struct {
    counter: u32 = 0,
    mutex: std.Io.Mutex = .init,
};

pub fn main(init: std.process.Init) !void {
    const io = init.io;
    var state = SharedState{};

    var group: std.Io.Group = .init;

    group.async(io, increment, .{ io, &state, 100 });
    group.async(io, increment, .{ io, &state, 100 });
    group.async(io, increment, .{ io, &state, 100 });
    group.async(io, increment, .{ io, &state, 100 });

    try group.await(io);

    print("Counter: {}n", .{state.counter});
}

fn increment(io: std.Io, state: *SharedState, times: u32) void {
    for (0..times) |_| {
        // Acquire the lock before modifying shared state.
        // What Mutex method blocks until the lock is acquired?
        state.mutex.lock(io) catch return;
        defer state.mutex.unlock(io); // <-- what's missing here?

        state.counter += 1;
    }
}

Exe: 092_async8.zig


//
// Tasks often need to communicate! Io provides Queue for this —
// a bounded, thread-safe channel for passing data between tasks:
//
//     var backing: [16]u32 = undefined;
//     var queue: std.Io.Queue(u32) = .init(&backing);
//
//     // Producer task:
//     try queue.putOne(io, value);    // blocks if queue is full
//
//     // Consumer task:
//     const val = try queue.getOne(io);  // blocks if queue is empty
//
// When the producer is done, it calls queue.close(io) to signal
// that no more data is coming. After that, getOne() will return
// error.Closed once the queue is drained.
//
// This is the classic producer/consumer pattern — one task
// generates work, another processes it, and the queue handles
// all the synchronization automatically.
//
// Fix this program: the producer sends numbers 1..10, the
// consumer sums them up. The expected sum is 55.
//
const std = @import("std");
const print = std.debug.print;

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    var backing: [4]u32 = undefined;
    var queue: std.Io.Queue(u32) = .init(&backing);

    var group: std.Io.Group = .init;

    group.async(io, producer, .{ io, &queue });
    group.async(io, consumer, .{ io, &queue });

    try group.await(io);
}

fn producer(io: std.Io, queue: *std.Io.Queue(u32)) void {
    // Send numbers 1 through 10 into the queue.
    for (1..11) |i| {
        // What Queue method sends a single element, blocking if full?
        queue.putOne(io, @intCast(i)) catch return;
    }
    // Signal that we're done sending.
    queue.close(io);
}

fn consumer(io: std.Io, queue: *std.Io.Queue(u32)) void {
    var sum: u32 = 0;
    while (true) {
        const value = queue.getOne(io) catch |err| switch (err) {
            error.Closed => break,
            error.Canceled => return,
        };
        sum += value;
    }
    print("Sum of 1..10 = {}n", .{sum});
}

Exe: 093_async9.zig


//
// We've been using io.async() to launch tasks. But there's a
// stronger variant: io.concurrent().
//
// The difference:
//
//   io.async():
//     * The function MAY run on a separate unit of concurrency,
//       or it may run immediately on the caller (synchronously).
//     * Never fails — if no concurrency is available, it just
//       runs the function right away.
//     * More portable, works with all Io backends.
//
//   io.concurrent():
//     * GUARANTEES a separate unit of concurrency.
//     * Can fail with error.ConcurrencyUnavailable if resources
//       are exhausted or the backend doesn't support it.
//     * Use when you NEED the task to run independently of the
//       caller.
//
// What is a "unit of concurrency"? That depends on the backend!
// The Threaded backend uses OS threads. But the Evented backends
// (Uring, Kqueue, Dispatch) use M:N green threads / fibers,
// which can provide concurrency even on a SINGLE OS thread.
// Your code doesn't need to know the difference.
//
// Because concurrent() can fail, you must handle the error:
//
//     var future = try io.concurrent(myFn, .{args});
//     defer _ = future.cancel(io);
//     const result = future.await(io);
//
// Let's try a slightly simplified example from signal processing:
// Suppose we're looking for the beginning of a signal above the noise
// level. To do this, we compare each entry from beginning to end with
// the threshold. To speed things up a bit, we split the signal into
// two halves and have two parallel workers search for them.
// Who finds the beginning first "wins" and thus ends the other one.
//
// As I said, this is a simplified explanation,
// but in practice it's done more or less like this.
//
const std = @import("std");
const Io = std.Io;
const print = std.debug.print;

const SearchResult = struct {
    found: bool,
    worker_id: u8 = 0,
    index: usize = 0,
};

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    const data = [_]u32{ 10, 23, 45, 67, 12, 69, 3, 54, 69, 42, 68, 56, 71, 79, 79, 75, 70, 77 };
    const threshold = 70;
    const mid = data.len / 2;

    // A queue with space for one result.
    var buf: [1]SearchResult = undefined;
    var queue = Io.Queue(SearchResult).init(&buf);

    // Launch two workers, each searching half the array.
    // Remember, we want them to be guaranteed separate units of concurrency.
    var f1 = try io.concurrent(searchThreshold, .{ io, data[0..mid], threshold, 0, 0, &queue });
    defer _ = f1.cancel(io);

    var f2 = try io.concurrent(searchThreshold, .{ io, data[mid..], threshold, mid, 1, &queue });
    defer _ = f2.cancel(io);

    // Wait for the first result.
    const result = try queue.getOne(io);

    if (result.found)
        print("Worker {} found signal start over threshold at index {}!n", .{ result.worker_id, result.index });
}

fn searchThreshold(
    io: Io,
    slice: []const u32,
    threshold: u32,
    base_offset: usize,
    worker_id: u8,
    queue: *Io.Queue(SearchResult),
) void {
    for (slice, 0..) |val, i| {
        // This pause is necessary so that the process can be canceled
        // if another one has already finished. Without this pause,
        // all workers would continue until the end.
        io.sleep(Io.Duration.fromMilliseconds(1), .awake) catch return;

        // To test this, you can uncomment this to view the work of the workers
        // and then comment out the pause.
        // print("id: {} - val: {}n", .{ worker_id, val });

        if (val >= threshold) {
            queue.putOne(io, .{
                .found = true,
                .worker_id = worker_id,
                .index = base_offset + i,
            }) catch return;
            return;
        }
    }

    // Nothing found
    queue.putOneUncancelable(io, .{ .found = false }) catch return;
}

Exe: 094_async10.zig


//
// In exercise 089, we learned that cancellation happens at
// "cancellation points" — any Io function that can return
// error.Canceled.
//
// But sometimes a task has a critical section that MUST NOT
// be interrupted — for example, writing a consistent state
// to disk, or completing a transaction.
//
// Io provides CancelProtection for this:
//
//     const old = io.swapCancelProtection(.blocked);
//     defer _ = io.swapCancelProtection(old);

//     // In this block, NO Io function will return error.Canceled.
//     // The cancel request is held until protection is restored.
//
// There are two states:
//   .unblocked — normal: cancellation points can fire (default)
//   .blocked   — protected: error.Canceled is never returned
//
// There's also io.checkCancel() — a pure cancellation point
// that does nothing except return error.Canceled if a cancel
// request is pending. Useful in long CPU-bound loops.
//
// And io.recancel() — re-arms a consumed cancel request so
// the NEXT cancellation point will fire again.
//
// Fix this program so the critical section completes even
// when the task is canceled.
//
const std = @import("std");
const print = std.debug.print;

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    var future = io.async(importantTask, .{io});
    defer _ = future.cancel(io);

    // Give the task time to start and enter its critical section.
    io.sleep(std.Io.Duration.fromMilliseconds(200), .awake) catch {};

    // Cancel while the task is in its protected section.
    const result = future.cancel(io);
    print("Task result: {s}n", .{result});
}

fn importantTask(io: std.Io) []const u8 {
    print("Starting critical section...n", .{});

    // Protect this section from cancellation.
    // What method swaps the cancel protection state?
    const old = io.swapCancelProtection(.blocked);
    defer _ = io.swapCancelProtection(old);

    // This sleep will NOT return error.Canceled even though
    // we get canceled during it — protection is active!
    io.sleep(std.Io.Duration.fromMilliseconds(300), .awake) catch |err| switch (err) {
        error.Canceled => {
            // This should never happen while protected!
            return "ERROR: canceled during critical section!";
        },
    };

    print("Critical section completed safely.n", .{});
    return "All data saved.";
}

Exe: 095_quiz_async.zig


//
// Quiz Time — Async I/O!
//
// Doctor Zoraptera's insect simulation is going well, but she
// realized that her virtual garden needs weather data! Insects
// behave differently depending on temperature, humidity, and
// wind conditions.
//
// She has set up three weather sensors around the garden that
// measure conditions in parallel and report their readings
// through a shared data channel. A collector task gathers the
// readings, and after all sensors have reported, a garden
// report is printed.
//
// But Doctor Z rushed through the code (she was being chased
// by a grasshopper) and left several bugs. Can you fix them?
//
// Here's what the program should do:
//   1. Three sensor tasks send exactly 3 readings each through
//      a Queue
//   2. A collector task receives readings concurrently,
//      protected by a Mutex
//   3. After all sensors finish, the queue is closed
//   4. The final report is written in a cancel-protected section
//
// *************************************************************
// *                A NOTE ABOUT THIS EXERCISE                 *
// *                                                           *
// * This quiz uses concepts from exercises 085-094.           *
// * There are 6 bugs to fix — look for the ???s!              *
// *                                                           *
// *************************************************************
//
const std = @import("std");
const print = std.debug.print;

const SensorType = enum { thermometer, hygrometer, anemometer };

const Reading = struct {
    sensor_type: SensorType,
    value: i32,
};

const GardenWeather = struct {
    temperature: i32 = 0,
    humidity: i32 = 0,
    wind: i32 = 0,
    readings_count: u32 = 0,
    mutex: std.Io.Mutex = .init,

    fn addReading(self: *GardenWeather, io: std.Io, reading: Reading) void {
        // Bug 1: The collector needs to lock before modifying
        // shared state. What Mutex method acquires the lock?
        self.mutex.lock(io) catch return;
        defer self.mutex.unlock(io);

        switch (reading.sensor_type) {
            .thermometer => self.temperature = reading.value,
            .hygrometer => self.humidity = reading.value,
            .anemometer => self.wind = reading.value,
        }
        self.readings_count += 1;
    }
};

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    var weather = GardenWeather{};

    var reading_buf: [8]Reading = undefined;
    var queue: std.Io.Queue(Reading) = .init(&reading_buf);

    // The collector must run concurrently so it can process
    // readings while the sensors are still sending.
    // Start it FIRST to ensure its concurrency unit is reserved.
    //
    // Bug 2: The collector needs guaranteed concurrency.
    // What method ensures a separate unit of concurrency?
    // (Don't forget: it can fail!)
    var collector_future = try io.concurrent(collector, .{ io, &queue, &weather });
    defer _ = collector_future.cancel(io);

    // Sensor group: the sensors can use async — they just need
    // to run, and async is more portable.
    var sensors: std.Io.Group = .init;

    sensors.async(io, sensor, .{ io, &queue, .thermometer, 20 });
    sensors.async(io, sensor, .{ io, &queue, .hygrometer, 60 });
    sensors.async(io, sensor, .{ io, &queue, .anemometer, 10 });

    // Bug 3: Wait for ALL sensors to finish sending their readings.
    // What Group method blocks until all tasks complete?
    try sensors.await(io);

    // All sensors done — close the queue so the collector knows
    // there's no more data coming.
    queue.close(io);

    // Bug 4: How do we wait for the collector to drain the remaining queue?
    _ = collector_future.await(io);

    // Now write the garden report. This is critical — it must
    // NOT be interrupted, even if something tries to cancel us!
    //
    // Bug 5: Protect this section from cancellation.
    // What Io method swaps the cancel protection state?
    const old_protection = io.swapCancelProtection(.blocked);
    defer _ = io.swapCancelProtection(old_protection);

    printGardenReport(&weather);
}

fn sensor(
    io: std.Io,
    queue: *std.Io.Queue(Reading),
    sensor_type: SensorType,
    base_value: i32,
) void {
    // Each sensor takes exactly 3 measurements.
    for (1..4) |i| {
        io.sleep(std.Io.Duration.fromMilliseconds(100), .awake) catch return;

        const reading = Reading{
            .sensor_type = sensor_type,
            .value = base_value + @as(i32, @intCast(i)),
        };

        // Bug 6: Send the reading into the queue.
        // What Queue method sends a single element?
        queue.putOne(io, reading) catch return;
    }
}

fn collector(
    io: std.Io,
    queue: *std.Io.Queue(Reading),
    weather: *GardenWeather,
) void {
    while (true) {
        const reading = queue.getOne(io) catch |err| switch (err) {
            error.Closed => break,
            error.Canceled => return,
        };
        weather.addReading(io, reading);
    }
}

fn printGardenReport(weather: *GardenWeather) void {
    print("=== Doctor Zoraptera's Garden Report ===n", .{});
    print("Temperature : {}Cn", .{weather.temperature});
    print("Humidity    : {}%n", .{weather.humidity});
    print("Wind        : {} km/hn", .{weather.wind});
    print("Readings    : {}n", .{weather.readings_count});

    if (weather.temperature > 20 and weather.wind < 15) {
        print("Bee-friendly conditions! Expect high pollination.n", .{});
    } else {
        print("Grasshoppers will be grumpy today.n", .{});
    }
}

// Further reading for the curious:
//
// This quiz covered the main async I/O primitives:
//   io.async()              - launch a task (may run inline)
//   io.concurrent()         - guaranteed unit of concurrency
//   Future.await/cancel     - collect or cancel a single task
//   Group.async/await/cancel - manage fire-and-forget tasks
//   Select.async/await      - race tasks, act on first completion
//   Queue                   - bounded channel between tasks
//   Mutex                   - protect shared state
//   CancelProtection        - shield critical sections
//
// There are more synchronization primitives we didn't cover:
//   Condition  - wait for a condition to become true
//   RwLock     - multiple readers OR one writer
//   Semaphore  - limit concurrent access to a resource
//   Futex      - low-level wait/wake on a memory address
//   Batch      - submit multiple I/O operations at once
//
// The key insight: all of these work through the Io VTable,
// so your code is portable across backends — whether Threaded
// (OS thread pool), or Evented (M:N green threads / fibers
// that can provide concurrency even on a single OS thread).
//
// Doctor Zoraptera approves.

Exe: 096_hello_c.zig


//
// When Andrew Kelley announced the idea of a new programming language
// - namely Zig - in his blog on February 8, 2016, he also immediately
// stated his ambitious goal: to replace the C language!
//
// In order to be able to achieve this goal at all, Zig should be
// as compatible as possible with its "predecessor".
// Only if it is possible to exchange individual modules in existing
// C programs without having to use complicated wrappers,
// the undertaking has a chance of success.
//
// So it is not surprising that calling C functions and vice versa
// is extremely "smooth".
//
// To call C functions in Zig, you only need to specify the library
// that contains said function. For this purpose there is a built-in
// function corresponding to the well-known @import():
//
//                           @cImport()
//
// All required libraries can now be included in the usual Zig notation:
//
//                    const c = @cImport({
//                        @cInclude("stdio.h");
//                        @cInclude("...");
//                    });
//
// Now a function can be called via the (in this example) constant 'c':
//
//                    c.puts("Hello world!");
//
// By the way, most C functions have return values in the form of an
// integer value. Errors can then be evaluated (return < 0) or other
// information can be obtained. For example, 'puts' returns the number
// of characters output.
//
// So that all this does not remain a dry theory now, let's just start
// and call a C function out of Zig.

// our well-known "import" for Zig
const std = @import("std");

// and here the new import for C
const c = @cImport({
    @cInclude("unistd.h");
});

pub fn main() void {

    // In order to output text that can be evaluated by the
    // Zig Builder, we need to write it to the Error output.
    // In Zig, we do this with "std.debug.print" and in C we can
    // specify a file descriptor i.e. 2 for error console.
    //
    // In this exercise we use 'write' to output 17 chars,
    // but something is still missing...
    const c_res = 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});
}
//
// 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".
//

Exe: 097_c_math.zig


//
// 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?
    @cInclude("math.h");
});

pub fn main() !void {
    const angle = 765.2;
    const circle = 360;

    // Here we call the C function 'fmod' to get our normalized angle.
    const result = c.fmod(angle, circle);

    // We use formatters for the desired precision and to truncate the decimal places
    std.debug.print("The normalized angle of {d: >3.1} degrees is {d: >3.1} degrees.n", .{ angle, result });
}

Exe: 098_for3.zig


//
// The Zig language is in rapid development and continuously
// improves the language constructs. Ziglings evolves with it.
//
// Until version 0.11, Zig's 'for' loops did not directly
// replicate the functionality of the C-style: "for(a;b;c)"
// which are so well suited for iterating over a numeric
// sequence.
//
// Instead, 'while' loops with counters clumsily stood in their
// place:
//
//     var i: usize = 0;
//     while (i < 10) : (i += 1) {
//         // Here variable 'i' will have each value 0 to 9.
//     }
//
// But here we are in the glorious future and Zig's 'for' loops
// can now take this form:
//
//     for (0..10) |i| {
//         // Here variable 'i' will have each value 0 to 9.
//     }
//
// The key to understanding this example is to know that '0..9'
// uses the new range syntax:
//
//     0..10 is a range from 0 to 9
//     1..4  is a range from 1 to 3
//
//     Crucially, the end value is EXCLUSIVE.
//
// At the moment, ranges in loops are only supported in 'for' loops.
//
// Perhaps you recall Exercise 13? We were printing a numeric
// sequence like so:
//
//     var n: u32 = 1;
//
//     // I want to print every number between 1 and 20 that is NOT
//     // divisible by 3 or 5.
//     while (n <= 20) : (n += 1) {
//         // The '%' symbol is the "modulo" operator and it
//         // returns the remainder after division.
//         if (n % 3 == 0) continue;
//         if (n % 5 == 0) continue;
//         std.debug.print("{} ", .{n});
//     }
//
//  Let's try out the new form of 'for' to re-implement that
//  exercise:
//
const std = @import("std");

pub fn main() void {

    // I want to print every number between 1 and 20 that is NOT
    // divisible by 3 or 5.
    for (1..21) |n| {

        // The '%' symbol is the "modulo" operator and it
        // returns the remainder after division.
        if (n % 3 == 0) continue;
        if (n % 5 == 0) continue;
        std.debug.print("{} ", .{n});
    }

    std.debug.print("n", .{});

    // Let's also print every number from 1 through 15
    for (1..16) |n| {
        std.debug.print("{} ", .{n});
    }
    std.debug.print("n", .{});
}
//
// That's a bit nicer, right?
//
// Of course, both 'while' and 'for' have different advantages.
// Exercises 11, 12, and 14 would NOT be simplified by switching
// a 'while' for a 'for'.

Exe: 099_memory_allocation.zig


//
// In most of the examples so far, the inputs are known at compile
// time, thus the amount of memory used by the program is fixed.
// However, if responding to input whose size is not known at compile
// time, such as:
//  - user input via command-line arguments
//  - inputs from another program
//
// You'll need to request memory for your program to be allocated by
// your operating system at runtime.
//
// Zig provides several different allocators. In the Zig
// documentation, it recommends the Arena allocator for simple
// programs which allocate once and then exit:
//
//     const std = @import("std");
//
//     // memory allocation can fail, so the return type is !void
//     pub fn main() !void {
//
//         var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
//         defer arena.deinit();
//
//         const allocator = arena.allocator();
//
//         const ptr = try allocator.create(i32);
//         std.debug.print("ptr={*}n", .{ptr});
//
//         const slice_ptr = try allocator.alloc(f64, 5);
//         std.debug.print("slice_ptr={*}n", .{slice_ptr});
//     }

// Instead of a simple integer or a slice with a constant size,
// this program requires allocating a slice that is the same size
// as an input array.

// Given a series of numbers, take the running average. In other
// words, each item N should contain the average of the last N
// elements.

const std = @import("std");

fn runningAverage(arr: []const f64, avg: []f64) void {
    var sum: f64 = 0;

    for (0.., arr) |index, val| {
        sum += val;
        const f_index: f64 = @floatFromInt(index + 1);
        avg[index] = sum / f_index;
    }
}

pub fn main() !void {
    // pretend this was defined by reading in user input
    const arr: []const f64 = &[_]f64{ 0.3, 0.2, 0.1, 0.1, 0.4 };

    // initialize the allocator
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);

    // free the memory on exit
    defer arena.deinit();

    // initialize the allocator
    const allocator = arena.allocator();

    // allocate memory for this array
    const avg: []f64 = try allocator.alloc(f64, arr.len);

    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

Exe: 100_bit_manipulation.zig


//
// Bit manipulation is a very powerful tool, also from Zig.
// Since the dawn of the computer age, numerous algorithms have been
// developed that solve tasks solely by moving, setting, or logically
// combining bits.
//
// Zig also uses direct bit manipulation in its standard library for
// functions where possible. And it is often possible with calculations
// based on integers.
//
// At first glance, it is often not easy to understand what exactly these
// algorithms do when only "numbers" in memory areas change outwardly.
// However, it should never be forgotten that the numbers only represent
// the interpretation of the bit sequences.
//
// Quasi the reversed case we have otherwise, namely that we represent
// numbers in bit sequences.
//
// We remember: 1 byte = 8 bits = 11111111 = 255 decimal = FF hex.
//
// Zig provides all the necessary functions to change the bits inside
// a variable. It is distinguished whether the bit change leads to an
// overflow or not. The details are in the Zig documentation in section
// "Table of Operators".
//
// Here are some examples of how the bits of variables can be changed:
//
//          const numOne: u8 = 15;        //   =  0000 1111
//          const numTwo: u8 = 245;       //   =  1111 0101
//
//          const res1 = numOne >> 4;     //   =  0000 0000 - shift right
//          const res2 = numOne << 4;     //   =  1111 0000 - shift left
//          const res3 = numOne & numTwo; //   =  0000 0101 - and
//          const res4 = numOne | numTwo; //   =  1111 1111 - or
//          const res5 = numOne ^ numTwo; //   =  1111 1010 - xor
//
//
// To familiarize ourselves with bit manipulation, we start with a simple
// but often underestimated function and then add other exercises in
// loose order.
//
// The following text contains excerpts from Wikipedia.
//
// Swap
// In computer programming, the act of swapping two variables refers to
// mutually exchanging the values of the variables. Usually, this is
// done with the data in memory. For example, in a program, two variables
// may be defined thus (in pseudocode):
//
//                        data_item x := 1
//                        data_item y := 0
//
//                        swap (x, y);
//
// After swap() is performed, x will contain the value 0 and y will
// contain 1; their values have been exchanged. The simplest and probably
// most widely used method to swap two variables is to use a third temporary
// variable:
//
//                        define swap (x, y)
//                        temp := x
//                        x := y
//                        y := temp
//
// However, with integers we can also achieve the swap function simply by
// bit manipulation. To do this, the variables are mutually linked with xor
// and the result is the same.

const std = @import("std");
const print = std.debug.print;

pub fn main() !void {

    // Let us use 1101 and 1011 as values for x and y
    var x: u8 = 0b1101;
    var y: u8 = 0b1011;

    // Now we swap the values of the two variables by doing xor on them
    x ^= y;
    y ^= x;

    // What must be written here?
    x ^= y;

    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.

Exe: 101_bit_manipulation2.zig


//
// Another useful application for bit manipulation is setting bits as flags.
// This is especially useful when processing lists of something and storing
// the states of the entries, e.g. a list of numbers and for each prime
// number a flag is set.
//
// As an example, let's take the Pangram exercise from Exercism:
// https://exercism.org/tracks/zig/exercises/pangram
//
// A pangram is a sentence using every letter of the alphabet at least once.
// It is case insensitive, so it doesn't matter if a letter is lower-case
// or upper-case. The best known English pangram is:
//
//           "The quick brown fox jumps over the lazy dog."
//
// There are several ways to select the letters that appear in the pangram
// (and it doesn't matter if they appear once or several times).
//
// For example, you could take an array of bool and set the value to 'true'
// for each letter in the order of the alphabet (a=0; b=1; etc.) found in
// the sentence. However, this is neither memory efficient nor particularly
// fast. Instead we choose a simpler approach that is very similar in principle:
// We define a variable with at least 26 bits (e.g. u32) and set the bit for
// each letter that is found in the corresponding position.
//
// Zig provides functions for this in the standard library, but we prefer to
// solve it without these extras, after all we want to learn something.
//
const std = @import("std");
const ascii = std.ascii;
const print = std.debug.print;

pub fn main() !void {
    // let's check the pangram
    print("Is this a pangram? {}!n", .{isPangram("The quick brown fox jumps over the lazy dog.")});
}

fn isPangram(str: []const u8) bool {
    // first we check if the string has at least 26 characters
    if (str.len < 26) return false;

    // we use a 32 bit variable of which we need 26 bits
    var bits: u32 = 0;

    // loop about all characters in the string
    for (str) |c| {
        // if the character is an alphabetical character
        if (ascii.isAscii(c) and ascii.isAlphabetic(c)) {
            // then we set the bit at the position
            //
            // to do this, we use a little trick:
            // since the letters in the ASCII table start at 65
            // and are numbered sequentially, we simply subtract the
            // first letter (in this case the 'a') from the character
            // found, and thus get the position of the desired bit
            bits |= @as(u32, 1) << @truncate(ascii.toLower(c) - 'a');
        }
    }
    // last we return the comparison if all 26 bits are set,
    // and if so, we know the given string is a pangram
    //
    // but what do we have to compare?
    return bits == 0x03FFFFFF;
}

Exe: 102_formatting.zig


//
// 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 print() function are the only
// way to definitively learn how to format strings in Zig:
//
//     https://ziglang.org/documentation/master/std/#std.Io.Writer.print
//
// 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. "nn" 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 print() accepts a
// variety of formatting instructions. It's basically a tiny
// language of its own. Here's a numeric example:
//
//     print("Catch-0x{x:0>4}.", .{twenty_two});
//
// This formatting instruction outputs a hexadecimal number with
// leading zeros:
//
//     Catch-0x0016.
//
// Or you can center-align a string like so:
//
//     print("{s:*^20}n", .{"Hello!"});
//
// Output:
//
//     *******Hello!*******
//
// Let's try making use of some formatting. We've decided that
// the one thing missing from our lives is a multiplication table
// for all numbers from 1-15. We want the table to be nice and
// neat, with numbers in straight columns like so:
//
//      X |  1   2   3   4   5  ...
//     ---+---+---+---+---+---+
//      1 |  1   2   3   4   5
//
//      2 |  2   4   6   8  10
//
//      3 |  3   6   9  12  15
//
//      4 |  4   8  12  16  20
//
//      5 |  5  10  15  20  25
//
//      ...
//
// Without string formatting, this would be a more challenging
// assignment because the number of digits in the numbers varies
// from 1 to 3. But formatting can help us with that.
//
const std = @import("std");
const print = std.debug.print;

pub fn main() !void {
    // Max number to multiply
    const size = 15;

    // Print the header:
    //
    // We start with a single 'X' for the diagonal.
    print("n X |", .{});

    // Header row with all numbers from 1 to size.
    for (0..size) |n| {
        print("{d:>3} ", .{n + 1});
    }
    print("n", .{});

    // Header column rule line.
    var n: u8 = 0;
    while (n <= size) : (n += 1) {
        print("---+", .{});
    }
    print("n", .{});

    // Now the actual table. (Is there anything more beautiful
    // than a well-formatted table?)
    for (0..size) |a| {
        print("{d:>2} |", .{a + 1});

        for (0..size) |b| {
            // What formatting is needed here to make our columns
            // nice and straight?
            print("{d:>3} ", .{(a + 1) * (b + 1)});
        }

        // After each row we use double line feed:
        print("nn", .{});
    }
}

Exe: 103_for4.zig


//
// 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, dec_nums) |hn, dn| {
        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.

Exe: 104_for5.zig


//
// 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, 1..) |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
//

Exe: 105_testing.zig


//
// A big advantage of Zig is the integration of its own test system.
// This allows the philosophy of Test Driven Development (TDD) to be
// implemented perfectly. Zig even goes one step further than other
// languages, the tests can be included directly in the source file.
//
// This has several advantages. On the one hand it is much clearer to
// have everything in one file, both the source code and the associated
// test code. On the other hand, it is much easier for third parties
// to understand what exactly a function is supposed to do if they can
// simply look at the test inside the source and compare both.
//
// Especially if you want to understand how e.g. the standard library
// of Zig works, this approach is very helpful. Furthermore it is very
// practical, if you want to report a bug to the Zig community, to
// illustrate it with a small example including a test.
//
// Therefore, in this exercise we will deal with the basics of testing
// in Zig. Basically, tests work as follows: you pass certain parameters
// to a function, for which you get a return - the result. This is then
// compared with the EXPECTED value. If both values match, the test is
// passed, otherwise an error message is displayed.
//
//          testing.expect(foo(param1, param2) == expected);
//
// Also other comparisons are possible, deviations or also errors can
// be provoked, which must lead to an appropriate behavior of the
// function, so that the test is passed.
//
// Tests can be run via Zig build system or applied directly to
// individual modules using "zig test xyz.zig".
//
// Both can be used script-driven to execute tests automatically, e.g.
// after checking into a Git repository. Something we also make extensive
// use of here at Ziglings.
//
const std = @import("std");
const testing = std.testing;

// This is a simple function that builds a sum from the passed parameters and
// returns.
fn add(a: f16, b: f16) f16 {
    return a + b;
}

// The associated test. It always starts with the keyword "test", followed by a
// description of the tasks of the test. This is followed by the test cases in
// curly brackets.
test "add" {

    // The first test checks if the sum of '41' and '1' gives '42', which is
    // correct.
    try testing.expect(add(41, 1) == 42);

    // Another way to perform this test is as follows:
    try testing.expectEqual(42, add(41, 1));

    // This time a test with the addition of a negative number:
    try testing.expect(add(5, -4) == 1);

    // And a floating point operation:
    try testing.expect(add(1.5, 1.5) == 3);
}

// Another simple function that returns the result of subtracting the two
// parameters.
fn sub(a: f16, b: f16) f16 {
    return a - b;
}

// The corresponding test is not much different from the previous one. Except
// that it contains an error that you need to correct.
test "sub" {
    try testing.expect(sub(10, 4) == 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.DivisionByZero, divide(15, 0));
}

Exe: 106_tokenization.zig


//
// The functionality of the standard library is becoming increasingly
// important in Zig. First of all, it is helpful to take a look at how
// the individual functions are implemented. Because this is wonderfully
// suitable as a template for your own functions. In addition, these
// standard functions are part of the basic configuration of Zig.
//
// This means that they are always available on every system.
// Therefore it is worthwhile to deal with them also in Ziglings.
// It's a great way to learn important skills. For example, it is
// often necessary to process large amounts of data from files.
// And for this sequential reading and processing, Zig provides some
// useful functions, which we will take a closer look at in the coming
// exercises.
//
// A nice example of this has been published on the Zig homepage,
// replacing the somewhat dusty 'Hello world!
//
// Nothing against 'Hello world!', but it just doesn't do justice
// to the elegance of Zig and that's a pity, if someone takes a short,
// first look at the homepage and doesn't get 'enchanted'. And for that
// the present example is simply better suited and we will therefore
// use it as an introduction to tokenizing, because it is wonderfully
// suited to understand the basic principles.
//
// In the following exercises we will also read and process data from
// large files, it will then be clearer to you how useful all this is.
//
// Let's start with the analysis of the example from the Zig homepage
// and explain the most important things.
//
//    const std = @import("std");
//
//    // Here a function from the Standard library is defined,
//    // which transfers numbers from a string into the respective
//    // integer values.
//    const parseInt = std.fmt.parseInt;
//
//    // Defining a test case
//    test "parse integers" {
//
//        // Four numbers are passed in a string.
//        // Please note that the individual values are separated
//        // either by a space or a comma.
//        const input = "123 67 89,99";
//
//        // In order to be able to process the input values,
//        // memory is required. An allocator is defined here for
//        // this purpose.
//        const gpa = std.testing.allocator;
//
//        // An array into which the numbers are stored is initialized.
//        var list: std.ArrayList(u32) = .empty;
//
//        // This way you can never forget what is urgently needed
//        // and the compiler doesn't grumble either.
//        defer list.deinit(gpa);
//
//        // Now it gets exciting:
//        // A standard tokenizer is called (Zig has several) and
//        // used to locate the positions of the respective separators
//        // (we remember, space and comma) and pass them to an iterator.
//        var it = std.mem.tokenizeAny(u8, input, " ,");
//
//        // The iterator can now be processed in a loop and the
//        // individual numbers can be transferred.
//        while (it.next()) |num| {
//            // But be careful: The numbers are still only available
//            // as strings. This is where the integer parser comes
//            // into play, converting them into real integer values.
//            const n = try parseInt(u32, num, 10);
//
//            // Finally the individual values are stored in the array.
//            try list.append(gpa, n);
//        }
//
//        // For the subsequent test, a second static array is created,
//        // which is directly filled with the expected values.
//        const expected = [_]u32{ 123, 67, 89, 99 };
//
//        // Now the numbers converted from the string can be compared
//        // with the expected ones, so that the test is completed
//        // successfully.
//        for (expected, list.items) |exp, actual| {
//            try std.testing.expectEqual(exp, actual);
//        }
//    }
//
// So much for the example from the homepage.
// Let's summarize the basic steps again:
//
// - We have a set of data in sequential order, separated from each other
//   by means of various characters.
//
// - For further processing, for example in an array, this data must be
//   read in, separated and, if necessary, converted into the target format.
//
// - We need a buffer that is large enough to hold the data.
//
// - This buffer can be created either statically at compile time, if the
//   amount of data is already known, or dynamically at runtime by using
//   a memory allocator.
//
// - The data are divided by means of Tokenizer at the respective
//   separators and stored in the reserved memory. This usually also
//   includes conversion to the target format.
//
// - Now the data can be conveniently processed further in the correct format.
//
// These steps are basically always the same.
// Whether the data is read from a file or entered by the user via the
// keyboard, for example, is irrelevant. Only subtleties are distinguished
// and that's why Zig has different tokenizers. But more about this in
// later exercises.
//
// Now we also want to write a small program to tokenize some data,
// after all we need some practice. Suppose we want to count the words
// of this little poem:
//
//      My name is Ozymandias, King of Kings;
//      Look on my Works, ye Mighty, and despair!
//            by Percy Bysshe Shelley
//
//
const std = @import("std");
const print = std.debug.print;

pub fn main() !void {

    // our input
    const poem =
        \My name is Ozymandias, King of Kings;
        \Look on my Works, ye Mighty, and despair!
    ;

    // now the tokenizer, but what do we need here?
    var it = std.mem.tokenizeAny(u8, poem, "n ,;!");

    // print all words and count them
    var cnt: usize = 0;
    while (it.next()) |word| {
        cnt += 1;
        print("{s}n", .{word});
    }

    // print the result
    print("This little poem has {d} words!n", .{cnt});
}

Exe: 107_threading.zig


//
// In Exercises 84-91, we learned about Zig's Io interface for
// concurrent execution: io.async(), Group, Select, and Futures.
// Under the hood, the Threaded backend manages a pool of real
// OS threads for you - including scheduling, cancellation, and
// resource cleanup.
//
// But sometimes you need direct control over threads:
//   * Long-lived dedicated workers
//   * Specific stack sizes or thread counts
//   * Code that doesn't have an Io interface available
//   * Fine-grained synchronization patterns
//
// That's where std.Thread comes in. It gives you a raw OS thread
// that you spawn, manage, and join yourself. No pool, no Futures,
// no automatic cancellation - but full control.
//
// The following diagram roughly illustrates the difference between
// the various types of process execution:
//
//
// Synchronous  Asynchronous
// Processing   Processing        Multithreading
// ┌──────────┐ ┌──────────┐  ┌──────────┐ ┌──────────┐
// │ Thread 1 │ │ Thread 1 │  │ Thread 1 │ │ Thread 2 │
// ├──────────┤ ├──────────┤  ├──────────┤ ├──────────┤    Overall Time
// └──┼┼┼┼┼───┴─┴──┼┼┼┼┼───┴──┴──┼┼┼┼┼───┴─┴──┼┼┼┼┼───┴──┬───────┬───────┬──
//    ├───┤        ├───┤         ├───┤        ├───┤      │       │       │
//    │ T │        │ T │         │ T │        │ T │      │       │       │
//    │ a │        │ a │         │ a │        │ a │      │       │       │
//    │ s │        │ s │         │ s │        │ s │      │       │       │
//    │ k │        │ k │         │ k │        │ k │      │       │       │
//    │   │        │   │         │   │        │   │      │       │       │
//    │ 1 │        │ 1 │         │ 1 │        │ 3 │      │       │       │
//    └─┬─┘        └─┬─┘         └─┬─┘        └─┬─┘      │       │       │
//      │            │             │            │      5 Sec     │       │
// ┌────┴───┐      ┌─┴─┐         ┌─┴─┐        ┌─┴─┐      │       │       │
// │Blocking│      │ T │         │ T │        │ T │      │       │       │
// └────┬───┘      │ a │         │ a │        │ a │      │       │       │
//      │          │ s │         │ s │        │ s │      │     8 Sec     │
//    ┌─┴─┐        │ k │         │ k │        │ k │      │       │       │
//    │ T │        │   │         │   │        │   │      │       │       │
//    │ a │        │ 2 │         │ 2 │        │ 4 │      │       │       │
//    │ s │        └─┬─┘         ├───┤        ├───┤      │       │       │
//    │ k │          │           │┼┼┼│        │┼┼┼│      ▼       │    10 Sec
//    │   │        ┌─┴─┐         └───┴────────┴───┴─────────     │       │
//    │ 1 │        │ T │                                         │       │
//    └─┬─┘        │ a │                                         │       │
//      │          │ s │                                         │       │
//    ┌─┴─┐        │ k │                                         │       │
//    │ T │        │   │                                         │       │
//    │ a │        │ 1 │                                         │       │
//    │ s │        ├───┤                                         │       │
//    │ k │        │┼┼┼│                                         ▼       │
//    │   │        └───┴────────────────────────────────────────────     │
//    │ 2 │                                                              │
//    ├───┤                                                              │
//    │┼┼┼│                                                              ▼
//    └───┴────────────────────────────────────────────────────────────────
//
//
// The diagram was modeled on the one in a blog in which the differences
// between asynchronous processing and multithreading are explained in detail:
// https://blog.devgenius.io/multi-threading-vs-asynchronous-programming-what-is-the-difference-3ebfe1179a5
//
// Our exercise is essentially about clarifying the approach in Zig and
// therefore we try to keep it as simple as possible.
// Multithreading in itself is already difficult enough. ;-)
//
const std = @import("std");

pub fn main() !void {
    // This is where the preparatory work takes place
    // before the parallel processing begins.
    std.debug.print("Starting work...n", .{});

    // These curly brackets are very important, they are necessary
    // to enclose the area where the threads are called.
    // Without these brackets, the program would not wait for the
    // end of the threads and they would continue to run beyond the
    // end of the program.
    {
        // Now we start the first thread, with the number as parameter
        const handle = try std.Thread.spawn(.{}, thread_function, .{1});

        // Waits for the thread to complete,
        // then deallocates any resources created on `spawn()`.
        defer handle.join();

        // Second thread
        const handle2 = try std.Thread.spawn(.{}, thread_function, .{2}); // that can't be right?
        defer handle2.join();

        // Third thread
        const handle3 = try std.Thread.spawn(.{}, thread_function, .{3});
        defer handle3.join(); // <-- something is missing

        // After the threads have been started,
        // they run in parallel and we can still do some work in between.
        var io_instance: std.Io.Threaded = .init_single_threaded;
        const io = io_instance.io();
        try io.sleep(std.Io.Duration.fromMilliseconds(400), .awake);
        std.debug.print("Some weird stuff, after starting the threads.n", .{});
    }
    // After we have left the closed area, we wait until
    // the threads have run through, if this has not yet been the case.
    std.debug.print("Zig is cool!n", .{});
}

// This function is started with every thread that we set up.
// In our example, we pass the number of the thread as a parameter.
fn thread_function(id: isize) !void {
    var io_instance: std.Io.Threaded = .init_single_threaded;
    const io = io_instance.io();
    try io.sleep(std.Io.Duration.fromMilliseconds(100 * @as(isize, @intCast(id))), .awake);
    std.debug.print("thread {d}: {s}n", .{ id, "started." });

    // This timer simulates the work of the thread.
    const work_time = 300 * ((5 - @rem(id, 3)) - 2);
    try io.sleep(std.Io.Duration.fromMilliseconds(@intCast(work_time)), .awake);

    std.debug.print("thread {d}: {s}n", .{ id, "finished." });
}
// This is the easiest way to run threads in parallel.
// In general, however, more management effort is required,
// e.g. by setting up a pool and allowing the threads to communicate
// with each other using semaphores.
//
// But that's a topic for another exercise.

Exe: 108_threading2.zig


//
// 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.
        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});
}

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.
//
// If count = 10_000_000_000_000 you should see the following:
// 3.141592653589

Exe: 109_files.zig


//
// Until now, we've only been printing our output in the console,
// which is good enough for fighting aliens and hermit bookkeeping.
//
// However, many other tasks require some interaction with the file system,
// which is the underlying structure for organizing files on your computer.
//
// The file system provides a hierarchical structure for storing files
// by organizing them into directories, which hold files and other directories,
// thus creating a tree structure that can be navigated.
//
// Fortunately, the Zig Standard Library provides a simple API for interacting
// with the file system, see the detail documentation here:
//
// https://ziglang.org/documentation/master/std/#std.Io
//
// In this exercise, we'll try to:
//   - create a new directory,
//   - open a file in the directory,
//   - write to the file.
//
// Note: For simplicity, we write byte-by-byte without buffering.
// In real applications, you'd typically use a buffer for better
// performance. We'll learn about buffered I/O in a later exercise.
//
const std = @import("std");

pub fn main(init: std.process.Init) !void {
    // default I/O implementation
    const io = init.io;

    // first we get the current working directory
    const cwd: std.Io.Dir = std.Io.Dir.cwd();

    // then we'll try to make a new directory /output/
    // to store our output files.
    cwd.createDir(io, "output", .default_dir) catch |e| switch (e) {
        // there is a chance you might want to run this
        // program more than once and the path might already
        // have been created, so we'll have to handle this error
        // by doing nothing
        //
        // we want to catch error.PathAlreadyExists and do nothing
        error.PathAlreadyExists => {},
        // if there's any other unexpected error we just propagate it through
        else => return e,
    };

    // then we'll try to open our freshly created directory
    // wait a minute...
    // opening a directory might fail!
    // what should we do here?
    var output_dir: std.Io.Dir = try cwd.openDir(io, "output", .{});
    defer output_dir.close(io);

    // we try to open the file `zigling.txt`,
    // and propagate any error up
    const file: std.Io.File = try output_dir.createFile(io, "zigling.txt", .{});
    // it is a good habit to close a file after you are done with it
    // so that other programs can read it and prevent data corruption
    // but here we are not yet done writing to the file
    // if only there were a keyword in Zig that
    // allowed you to "defer" code execution to the end of the scope...
    defer file.close(io);

    // you are not allowed to move these lines above the file closing line!
    var file_writer = file.writer(io, &.{});
    const writer = &file_writer.interface;

    const byte_written = try writer.write("It's zigling time!");
    std.debug.print("Successfully wrote {d} bytes.n", .{byte_written});
}
// to check if you actually write to the file, you can either,
// 1. open the file in your text editor, or
// 2. print the content of the file in the console with one of these commands
//    Linux/macOS:   >> cat ./output/zigling.txt
//    Windows (CMD): >> type .outputzigling.txt
//
//
// More on Creating files
//
// notice in:
// ... try output_dir.createFile(io, "zigling.txt", .{});
//                                                  ^^^
//                 we passed this anonymous struct to the function call
//
// this is the struct `CreateFlag` with default fields
// {
//      read: bool = false,
//      truncate: bool = true,
//      exclusive: bool = false,
//      lock: Lock = .none,
//      lock_nonblocking: bool = false,
//      mode: Mode = default_mode
// }
//
// Question:
//   - what should you do if you want to also read the file after opening it?
//   - go to the documentation of the struct `std.Io.Dir` here:
//     https://ziglang.org/documentation/master/std/#std.Io.Dir
//       - can you find a function for opening a file? how about deleting a file?
//       - what kind of options can you use with those functions?

Exe: 110_files2.zig


//
// Prerequisite :
//    - exercise/106_files.zig, or
//    - create a file {project_root}/output/zigling.txt
//      with content `It's zigling time!`(18 bytes total)
//
// Now there's no point in writing to a file if we don't read from it, am I right?
// Let's write a program to read the content of the file that we just created.
//
// I am assuming that you've created the appropriate files for this to work.
//
// Alright, bud, lean in close. Here's the game plan.
//    - First, we open the {project_root}/output/ directory
//    - Secondly, we open file `zigling.txt` in that directory
//    - Then, we initialize an array of characters with all letter 'A', and print it
//    - After that, we read the content of the file into the array
//    - Finally, we print out the content we just read
//
// Note: For simplicity, we read byte-by-byte without buffering.
// In real applications, you'd typically use a buffer for better
// performance. We'll learn about buffered I/O in a later exercise.

const std = @import("std");

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    // Get the current working directory
    const cwd = std.Io.Dir.cwd();

    // try to open ./output assuming you did your 106_files exercise
    var output_dir = try cwd.openDir(io, "output", .{});
    defer output_dir.close(io);

    // try to open the file
    const file = try output_dir.openFile(io, "zigling.txt", .{});
    defer file.close(io);

    // initialize an array of u8 with all letter 'A'
    // we need to pick the size of the array, 64 seems like a good number
    // fix the initialization below
    var content = [_]u8{'A'} ** 64;
    // this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`
    std.debug.print("{s}n", .{content});

    var file_reader = file.reader(io, &.{});
    const reader = &file_reader.interface;

    // okay, seems like a threat of violence is not the answer in this case
    // can you go here to find a way to read the content?
    // https://ziglang.org/documentation/master/std/#std.Io.Reader
    // hint: look for a method that reads into a slice
    const bytes_read = try reader.readSliceShort(&content);

    // Woah, too screamy. I know you're excited for zigling time but tone it down a bit.
    // Can you print only what we read from the file?
    std.debug.print("Successfully Read {d} bytes: {s}n", .{
        bytes_read,
        content[0..bytes_read], // change this line only
    });
}

Exe: 111_labeled_switch.zig


//
// 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 reachedn", .{});
//      }
//
// 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 reachedn", .{});
//      }
//
// 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", .{});
}

Exe: 112_vectors.zig


// So far in Ziglings, we've seen how for loops can be used to
// repeat calculations across an array in several ways.
//
// For loops are generally great for this kind of task, but
// sometimes they don't fully utilize the capabilities of the
// CPU.
//
// Most modern CPUs can execute instructions in which SEVERAL
// calculations are performed WITHIN registers at the SAME TIME.
// These are known as "single instruction, multiple data" (SIMD)
// instructions. SIMD instructions can make code significantly
// more performant.
//
// To see why, imagine we have a program in which we take the
// square root of four (changing) f32 floats.
//
// A simple compiler would take the program and produce machine code
// which calculates each square root sequentially. Most registers on
// modern CPUs have 64 bits, so we could imagine that each float moves
// into a 64-bit register, and the following happens four times:
//
//            32 bits   32 bits
//          +-------------------+
// register |    0    |    x    |
//          +-------------------+
//
//                    |
//           [SQRT instruction]
//                    V
//
//          +-------------------+
//          |    0    | sqrt(x) |
//          +-------------------+
//
// Notice that half of the register contains blank data to which
// nothing happened. What a waste! What if we were able to use
// that space instead? This is the idea at the core of SIMD.
//
// Most modern CPUs contain specialized registers with at least 128 bits
// for performing SIMD instructions. On a machine with 128-bit SIMD
// registers, a smart compiler would probably NOT issue four sqrt
// instructions as above, but instead pack the floats into a single
// 128-bit register, then execute a single "packed" sqrt
// instruction to do ALL the square root calculations at once.
//
// For example:
//
//
//             32 bits   32 bits   32 bits   32 bits
//           +---------------------------------------+
// register  |   4.0   |   9.0   |  25.0   |  49.0   |
//           +---------------------------------------+
//
//                              |
//                  [SIMD SQRT instruction]
//                              V
//
//           +---------------------------------------+
// register  |   2.0   |   3.0   |   5.0   |   7.0   |
//           +---------------------------------------+
//
// Pretty cool, right?
//
// Code with SIMD instructions is usually more performant than code
// without SIMD instructions. Zig cares a lot about performance,
// so it has built-in support for SIMD! It has a data structure that
// directly supports SIMD instructions:
//
//                        +-----------+
//                        |  Vectors  |
//                        +-----------+
//
// Operations performed on vectors in Zig will be done in parallel using
// SIMD instructions, whenever possible.
//
// Defining vectors in Zig is straightforwards. No library import is needed.
const v1 = @Vector(3, i32){ 1, 10, 100 };
const v2 = @Vector(3, f32){ 2.0, 3.0, 5.0 };

// Vectors support the same builtin operators as their underlying base types.
const v3 = v1 + v1; // {   2,  20,  200};
const v4 = v2 * v2; // { 4.0, 9.0, 25.0};

// Intrinsics that apply to base types usually extend to vectors.
const v5: @Vector(3, f32) = @floatFromInt(v3); // { 2.0,  20.0,  200.0}
const v6 = v4 - v5; // { 2.0, -11.0, -175.0}
const v7 = @abs(v6); // { 2.0,  11.0,  175.0}

// We can make constant vectors, and reduce vectors.
const v8: @Vector(4, u8) = @splat(2); // { 2, 2, 2, 2}
const v8_sum = @reduce(.Add, v8); // 8
const v8_min = @reduce(.Min, v8); // 2

// Fixed-length arrays can be automatically assigned to vectors (and vice-versa).
const single_digit_primes = [4]i8{ 2, 3, 5, 7 };
const prime_vector: @Vector(4, i8) = single_digit_primes;

// Now let's use vectors to simplify and optimize some code!
//
// Ewa is writing a program in which they frequently want to compare
// two lists of four f32s. Ewa expects the lists to be similar, and
// wants to determine the largest pairwise difference between the lists.
//
// Ewa wrote the following function to figure this out.

fn calcMaxPairwiseDiffOld(list1: [4]f32, list2: [4]f32) f32 {
    var max_diff: f32 = 0;
    for (list1, list2) |n1, n2| {
        const abs_diff = @abs(n1 - n2);
        if (abs_diff > max_diff) {
            max_diff = abs_diff;
        }
    }
    return max_diff;
}

// Ewa heard about vectors in Zig, and started writing a new vector
// version of the function, but has got stuck!
//
// Help Ewa finish the vector version! The examples above should help.

const Vec4 = @Vector(4, f32);
fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 {
    const abs_diff_vec = ???;
    const max_diff = @reduce(???, abs_diff_vec);
    return max_diff;
}

// Quite the simplification! We could even write the function in one line
// and it would still be readable.
//
// Since the entire function is now expressed in terms of vector operations,
// the Zig compiler will easily be able to compile it down to machine code
// which utilizes the all-powerful SIMD instructions and does a lot of the
// computation in parallel.

const std = @import("std");
const print = std.debug.print;

pub fn main() void {
    const l1 = [4]f32{ 3.141, 2.718, 0.577, 1.000 };
    const l2 = [4]f32{ 3.154, 2.707, 0.591, 0.993 };
    const mpd_old = calcMaxPairwiseDiffOld(l1, l2);
    const mpd_new = calcMaxPairwiseDiffNew(l1, l2);
    print("Max difference (old fn): {d: >5.3}n", .{mpd_old});
    print("Max difference (new fn): {d: >5.3}n", .{mpd_new});
}

Exe: 113_quiz9.zig


// ----------------------------------------------------------------------------
// 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 PORTBn", .{});
    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 PORTBn", .{});
    print("-------------------------n", .{});

    PORTB = 0b1001; // reset PORTB
    print("  {b:0>4} // (initial state of PORTB)n", .{PORTB});
    print("| {b:0>4} // (bitmask)n", .{0b0100});
    PORTB = PORTB ??? (1 << 2); // What's missing here?
    checkAnswer(0b1101, PORTB);

    newline();

    PORTB = 0b1001; // reset PORTB
    print("  {b:0>4} // (reset state)n", .{PORTB});
    print("| {b:0>4} // (bitmask)n", .{0b0100});
    PORTB ??? (1 << 2); // What's missing here?
    checkAnswer(0b1101, PORTB);

    newline();

    print("Clear pins with AND and NOT on PORTBn", .{});
    print("------------------------------------n", .{});

    PORTB = 0b1110; // reset PORTB
    print("  {b:0>4} // (initial state of PORTB)n", .{PORTB});
    print("& {b:0>4} // (bitmask)n", .{0b1011});
    PORTB = PORTB & ???@as(u4, 1 << 2); // What character is missing here?
    checkAnswer(0b1010, PORTB);

    newline();

    PORTB = 0b0111; // reset PORTB
    print("  {b:0>4} // (reset state)n", .{PORTB});
    print("& {b:0>4} // (bitmask)n", .{0b1110});
    PORTB &= ~(1 << 0); // What's missing here?
    checkAnswer(0b0110, PORTB);

    newline();
    newline();
}

// ************************************************************************
//                    IN-DEPTH EXPLANATIONS BELOW
// ************************************************************************
//
//
//
//
//
//
//
//
//
//
//
// ------------------------------------------------------------------------
// Toggling bits with XOR:
// ------------------------------------------------------------------------
// XOR stands for "exclusive or". We can toggle bits with the ^ (XOR)
// bitwise operator, like so:
//
//
// In order to output a 1, the logic of an XOR operation requires that the
// two input bits are of different values. Therefore, 0 ^ 1 and 1 ^ 0 will
// both yield a 1 but 0 ^ 0 and 1 ^ 1 will output 0. XOR's unique behavior
// of outputting a 0 when both inputs are 1s is what makes it different from
// the OR operator; it also gives us the ability to toggle bits by putting
// 1s into our bitmask.
//
// - 1s in our bitmask operand, can be thought of as causing the
//   corresponding bits in the other operand to flip to the opposite value.
// - 0s cause no change.
//
//                            The 0s in our bitmask preserve these values
// -XOR op- ---expanded---    in the output.
//            _______________/
//           /       /
//   1100   1   1   0   0
// ^ 0101   0   1   0   1 (bitmask)
// ------   -   -   -   -
// = 1001   1   0   0   1 <- This bit was already cleared.
//              _______
//                       
//                         We can think of these bits having flipped
//                         because of the presence of 1s in those columns
//                         of our bitmask.
//
// Now let's take a look at setting bits with the | operator.
//
//
//
//
//
// ------------------------------------------------------------------------
// Setting bits with OR:
// ------------------------------------------------------------------------
// We can set bits on PORTB with the | (OR) operator, like so:
//
// var PORTB: u4 = 0b1001;
// PORTB = PORTB | 0b0010;
// print("PORTB: {b:0>4}n", .{PORTB}); // output: 1011
//
// -OR op-  ---expanded---
//                    _ Set only this bit.
//                   /
//   1001   1   0   0   1
// | 0010   0   0   1   0 (bitmask)
// ------   -   -   -   -
// = 1011   1   0   1   1
//           __________
//                        
//                          These bits remain untouched because OR-ing with
//                          a 0 effects no change.
//
// ------------------------------------------------------------------------
// To create a bitmask like 0b0010 used above:
//
// 1. First, shift the value 1 over one place with the bitwise << (shift
// left) operator as indicated below:
//           1 << 0 -> 0001
//           1 << 1 -> 0010  <-- Shift 1 one place to the left
//           1 << 2 -> 0100
//           1 << 3 -> 1000
//
// This allows us to rewrite the above code like this:
//
// var PORTB: u4 = 0b1001;
// PORTB = PORTB | (1 << 1);
// print("PORTB: {b:0>4}n", .{PORTB}); // output: 1011
//
// Finally, as in the C language, Zig allows us to use the |= operator, so
// we can rewrite our code again in an even more compact and idiomatic
// form: PORTB |= (1 << 1)

// So now we've covered how to toggle and set bits. What about clearing
// them? Well, this is where Zig throws us a curve ball. Don't worry we'll
// go through it step by step.
//
//
//
//
//
// ------------------------------------------------------------------------
// Clearing bits with AND and NOT:
// ------------------------------------------------------------------------
// We can clear bits with the & (AND) bitwise operator, like so:

// PORTB = 0b1110; // reset PORTB
// PORTB = PORTB & 0b1011;
// print("PORTB: {b:0>4}n", .{PORTB}); // output -> 1010
//
// - 0s clear bits when used in conjunction with a bitwise AND.
// - 1s do nothing, thus preserving the original bits.
//
// -AND op- ---expanded---
//                __________ Clear only this bit.
//               /
//   1110   1   1   1   0
// & 1011   1   0   1   1 (bitmask)
// ------   -   -   -   -
// = 1010   1   0   1   0 <- This bit was already cleared.
//           _______
//                    
//                      These bits remain untouched because AND-ing with a
//                      1 preserves the original bit value whether 0 or 1.
//
// ------------------------------------------------------------------------
// We can use the ~ (NOT) operator to easily create a bitmask like 1011:
//
//  1. First, shift the value 1 over two places with the bit-wise << (shift
//     left) operator as indicated below:
//          1 << 0 -> 0001
//          1 << 1 -> 0010
//          1 << 2 -> 0100 <- The 1 has been shifted two places to the left
//          1 << 3 -> 1000
//
//  2. The second step in creating our bitmask is to invert the bits
//          ~0100 -> 1011
//     in C we would write this as:
//          ~(1 << 2) -> 1011
//
//     But if we try to compile ~(1 << 2) in Zig, we'll get an error:
//          unable to perform binary not operation on type 'comptime_int'
//
//     Before Zig can invert our bits, it needs to know the number of
//     bits it's being asked to invert.
//
//     We do this with the @as (cast as) built-in like this:
//          @as(u4, 1 << 2) -> 0100
//
//     Finally, we can invert our new mask by placing the NOT ~ operator
//     before our expression, like this:
//          ~@as(u4, 1 << 2) -> 1011
//
//     If you are offput by the fact that you can't simply invert bits like
//     you can in languages such as C without casting to a particular size
//     of integer, you're not alone. However, this is actually another
//     instance where Zig is really helpful because it protects you from
//     difficult to debug integer overflow bugs that can have you tearing
//     your hair out. In the interest of keeping things sane, Zig requires
//     you simply to tell it the size of number you are inverting. In the
//     words of Andrew Kelley, "If you want to invert the bits of an
//     integer, zig has to know how many bits there are."
//
//     For more insight into the Zig team's position on why the language
//     takes the approach it does with the ~ operator, take a look at
//     Andrew's comments on the following github issue:
//     https://github.com/ziglang/zig/issues/1382#issuecomment-414459529
//
// Whew, so after all that what we end up with is:
//          PORTB = PORTB & ~@as(u4, 1 << 2);
//
// We can shorten this with the &= combined AND and assignment operator,
// which applies the AND operator on PORTB and then reassigns PORTB. Here's
// what that looks like:
//          PORTB &= ~@as(u4, 1 << 2);
//

// ------------------------------------------------------------------------
// Conclusion
// ------------------------------------------------------------------------
//
// While the examples in this quiz have used only 4-bit wide variables,
// working with 8 bits is no different. Here's an example where we set
// every other bit beginning with the two's place:

// var PORTD: u8 = 0b0000_0000;
// print("PORTD: {b:0>8}n", .{PORTD});
// PORTD |= (1 << 1);
// PORTD = setBit(u8, PORTD, 3);
// PORTD |= (1 << 5) | (1 << 7);
// print("PORTD: {b:0>8} // set every other bitn", .{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 bitn", .{PORTD});
// PORTD = ~PORTD;
// print("PORTD: {b:0>8} // bits flipped with NOT (~)n", .{PORTD});
// newline();

// ----------------------------------------------------------------------------
// Here are some helper functions for manipulating bits
// ----------------------------------------------------------------------------

// Functions for setting, clearing, and toggling a single bit
fn setBit(comptime T: type, byte: T, comptime bit_pos: T) !T {
    return byte | (1 << bit_pos);
}

test "setBit" {
    try testing.expectEqual(setBit(u8, 0b0000_0000, 3), 0b0000_1000);
}

fn clearBit(comptime T: type, byte: T, comptime bit_pos: T) T {
    return byte & ~@as(T, (1 << bit_pos));
}

test "clearBit" {
    try testing.expectEqual(clearBit(u8, 0b1111_1111, 0), 0b1111_1110);
}

fn toggleBit(comptime T: type, byte: T, comptime bit_pos: T) T {
    return byte ^ (1 << bit_pos);
}

test "toggleBit" {
    var byte = toggleBit(u8, 0b0000_0000, 0);
    try testing.expectEqual(byte, 0b0000_0001);
    byte = toggleBit(u8, byte, 0);
    try testing.expectEqual(byte, 0b0000_0000);
}

// ----------------------------------------------------------------------------
// Some additional functions for setting, clearing, and toggling multiple bits
// at once with a tuple because, hey, why not?
// ----------------------------------------------------------------------------
//

fn createBitmask(comptime T: type, comptime bits: anytype) !T {
    comptime var bitmask: T = 0;
    inline for (bits) |bit| {
        if (bit >= @bitSizeOf(T)) return error.BitPosTooLarge;
        if (bit < 0) return error.BitPosTooSmall;

        bitmask |= (1 << bit);
    }
    return bitmask;
}

test "creating bitmasks from a tuple" {
    try testing.expectEqual(createBitmask(u8, .{0}), 0b0000_0001);
    try testing.expectEqual(createBitmask(u8, .{1}), 0b0000_0010);
    try testing.expectEqual(createBitmask(u8, .{2}), 0b0000_0100);
    try testing.expectEqual(createBitmask(u8, .{3}), 0b0000_1000);
    //
    try testing.expectEqual(createBitmask(u8, .{ 0, 4 }), 0b0001_0001);
    try testing.expectEqual(createBitmask(u8, .{ 1, 5 }), 0b0010_0010);
    try testing.expectEqual(createBitmask(u8, .{ 2, 6 }), 0b0100_0100);
    try testing.expectEqual(createBitmask(u8, .{ 3, 7 }), 0b1000_1000);

    try testing.expectError(error.BitPosTooLarge, createBitmask(u4, .{4}));
}

fn setBits(byte: u8, bits: anytype) !u8 {
    const bitmask = try createBitmask(u8, bits);
    return byte | bitmask;
}

test "setBits" {
    try testing.expectEqual(setBits(0b0000_0000, .{0}), 0b0000_0001);
    try testing.expectEqual(setBits(0b0000_0000, .{7}), 0b1000_0000);

    try testing.expectEqual(setBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);
    try testing.expectEqual(setBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);

    try testing.expectEqual(setBits(0b0000_0000, .{ 2, 3, 4, 5 }), 0b0011_1100);

    try testing.expectError(error.BitPosTooLarge, setBits(0b1111_1111, .{8}));
    try testing.expectError(error.BitPosTooSmall, setBits(0b1111_1111, .{-1}));
}

fn clearBits(comptime byte: u8, comptime bits: anytype) !u8 {
    const bitmask: u8 = try createBitmask(u8, bits);
    return byte & ~@as(u8, bitmask);
}

test "clearBits" {
    try testing.expectEqual(clearBits(0b1111_1111, .{0}), 0b1111_1110);
    try testing.expectEqual(clearBits(0b1111_1111, .{7}), 0b0111_1111);

    try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
    try testing.expectEqual(clearBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);

    try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 6, 7 }), 0b0011_1100);

    try testing.expectError(error.BitPosTooLarge, clearBits(0b1111_1111, .{8}));
    try testing.expectError(error.BitPosTooSmall, clearBits(0b1111_1111, .{-1}));
}

fn toggleBits(comptime byte: u8, comptime bits: anytype) !u8 {
    const bitmask = try createBitmask(u8, bits);
    return byte ^ bitmask;
}

test "toggleBits" {
    try testing.expectEqual(toggleBits(0b0000_0000, .{0}), 0b0000_0001);
    try testing.expectEqual(toggleBits(0b0000_0000, .{7}), 0b1000_0000);

    try testing.expectEqual(toggleBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
    try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);

    try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_0000);
    try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3 }), 0b0000_0000);

    try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 2, 4, 6 }), 0b0101_0101);

    try testing.expectError(error.BitPosTooLarge, toggleBits(0b1111_1111, .{8}));
    try testing.expectError(error.BitPosTooSmall, toggleBits(0b1111_1111, .{-1}));
}

// ----------------------------------------------------------------------------
// Utility functions
// ----------------------------------------------------------------------------

fn newline() void {
    print("n", .{});
}

fn checkAnswer(expected: u4, answer: u4) void {
    if (expected != answer) {
        print("*************************************************************n", .{});
        print("= {b:0>4} <- INCORRECT! THE EXPECTED OUTPUT IS {b:0>4}n", .{ answer, expected });
        print("*************************************************************n", .{});
    } else {
        print("= {b:0>4}", .{answer});
    }
    newline();
}

Exe: 114_packed.zig


//
// We've already learned plenty about bit manipulation using bitwise operations
// in exercices 097 and 098 and in quiz 110. The techniques we already know work
// just fine, but creating masks and shifting individual bits around can become
// quite tedious and unwieldy pretty quickly.
// What if there was a better, a more convenient way to control invidivual bits?
//
// Luckily, Zig has a keyword for exactly this purpose:
//
//     packed
//
// It doesn't do anything on its own, to unlock its potential (and to get our
// program to compile) we have to attach it either to a struct or to a union
// declaration:
//
//     const Foo = packed struct { ... };
//     const Bar = packed union { ... };
//
// Now, what does this keyword even do?
// To answer this question we first have to talk about *container layouts*.
//
// Plain structs and unions use the `auto` layout; it gives no guarantees about
// their size or the order of the fields they contain, both are fully up to the
// compiler (though both size and field order *are* guaranteed to be the same
// across any single compilation unit).
//
// Attaching the `packed` keyword to a container makes it use `packed` layout:
// Suddenly, all of its fields are *packed* together tightly without any padding
// in between and their order is guaranteed to be the same as the one specified
// in our source code. For structs, the size of the container is guaranteed to
// be the sum of the (bit-)sizes of all of its fields. For unions, all fields
// have to have the exact same (bit-)size (no padding allowed!); the union itself
// is also guaranteed to be exactly of this size.
//
// If you're familiar with C, you might have already heard of structure packing
// in a different context: arranging fields in a way that minimizes the amount
// of alignment padding between them (or having the compiler do it for you).
// This is *not* what Zig's `packed` keyword is for!
//
// Try to make the comptime assertions below pass:

const PackedStruct = packed struct {
    a: u2,
    b: u?,
};

comptime {
    assert(@bitSizeOf(PackedStruct) == 6);
}

const PackedUnion = packed union {
    a: bool,
    b: u?,
};

comptime {
    assert(@bitSizeOf(PackedUnion) == 1);
}

// Now, how can we use this new knowledge to manipulate some bits?
//
// As you might have already guessed, `packed` containers are very useful for
// representing bitflags or other tightly packed collections of bit-sized values
// often found in file headers and network protocols.
//
// Let's take a look at a real-life example:
// The LZ4 compression format (†) specifies a frame format to describe compressed
// data. Each LZ4 frame has a descriptor, and each descriptor contains a 'FLG'
// byte that specifies the contents of its frame:

/// |  BitNb  |  7-6  |   5   |    4     |  3   |    2     |   1    |   0  |
/// | ------- |-------|-------|----------|------|----------|--------|------|
/// |FieldName|Version|B.Indep|B.Checksum|C.Size|C.Checksum|Reserved|DictID|
///
const FLG = packed struct(u8) {
    dict_id: bool,
    reserved: u1 = 0,
    content_checksum: bool,
    content_size: bool,
    block_checksum: bool,
    block_indepencence: bool,
    version: u2,
};

// Wait, what's with the `(u8)` after the `struct` keyword? What do integers have
// to do with all of this?
// Well, this is a good opportunity to come clear about something:
// packed structs and packed unions aren't actually structs or unions at all...
// They are merely integers in disguise! For all intents and purposes, their
// fields are just convenient names for ranges of their underlying bits. To make
// it easier to enforce size requirements for packed containers, Zig allows us
// to specify a *backing integer* for them, just like for enums.
//
// In the case of `FLG`, we want our struct to occupy exactly a single byte, so
// we specify `u8` as the backing integer. It's safe to convert between a packed
// container and its backing integer using the builtin `@bitCast`.
// The LZ4 spec also mandates that reserved bits must always be zero, so it's
// good practice to set `0` as a default value for `reserved`.
//
// The fields of a packed struct start at the least significant bit of its backing
// integer and end at its most significant bit. This is the case no matter what
// endianness our target has.
//
// Try to silence the complaints below:

const Bits = packed struct(u4) {
    a: u1 = 0,
    b: u1 = 0,
    c: u1 = 0,
    d: u1 = 0,
};

pub fn main() void {
    {
        const expected: Bits = @bitCast(@as(u4, 0b1000));
        const my_bits: Bits = .{};
        if (my_bits != expected) complain(my_bits, expected, @src());
    }

    {
        const expected: Bits = @bitCast(@as(u4, 0b0001));
        const my_bits: Bits = .{};
        if (my_bits != expected) complain(my_bits, expected, @src());
    }

    {
        const expected: Bits = @bitCast(@as(u4, 0b0010));
        const my_bits: Bits = .{};
        if (my_bits != expected) complain(my_bits, expected, @src());
    }

    {
        const expected: Bits = @bitCast(@as(u4, 0b0011));
        const my_bits: Bits = .{};
        if (my_bits != expected) complain(my_bits, expected, @src());
    }

    {
        const expected: Bits = @bitCast(@as(u4, 0b1101));
        const my_bits: Bits = .{};
        if (my_bits != expected) complain(my_bits, expected, @src());
    }
}

// As we can see, equality comparisons (`==` and `!=`) work for packed structs.
// They also work for packed unions. However, since packed containers are not
// naturally ordered, we can't use any other comparison operators on them.
//
// It's also possible to use packed containers in `switch` statements, which we
// will cover in the next exercise!
//
// Since packed containers make very strong guarantees about their memory layout,
// only a handful of types are eligible to be part of them.
// The following types are allowed as field types:
//
// - integers
// - floats
// - bool
// - void
// - enums with explicit backing integers
// - packed unions
// - packed structs
//

const std = @import("std");
const assert = std.debug.assert;

fn complain(my_bits: Bits, expected: Bits, src_loc: std.builtin.SourceLocation) void {
    std.debug.print(
        "That's not quite right! You've got 0b{b:0>4}, but we want 0b{b:0>4} in line {d}.n",
        .{ @as(u4, @bitCast(my_bits)), @as(u4, @bitCast(expected)), src_loc.line },
    );
}

// (†) https://github.com/lz4/lz4/blob/5c4c1fb2354133e1f3b087a341576985f8114bd5/doc/lz4_Frame_format.md#frame-descriptor

Exe: 115_packed2.zig


//
// We've already learned about switch statements in exercises 030, 031 and 108.
// They also work with packed containers:

const S = packed struct(u2) {
    a: bool,
    b: i1,
};

// Try to make it compile without adding an `else` prong!

comptime {
    const s: S = .{ .a = true, .b = -1 };
    switch (s) {
        .{ .a = true, .b = -1 } => {}, // ok!
        .{ .a = true, .b = ??? },
        .{ .a = ???, .b = 0 },
        .{ .a = ???, .b = ??? },
        => @compileError("We don't want to end up here!"),
    }
}

// As we can see, switching on packed structs is pretty straightforward.
// When switching on packed unions however, we'll realize that a packed
// union never keeps track of its active tag, not even in debug mode! This
// means that packed unions compare solely by their bit pattern (again, just
// like integers).

const U = packed union(u2) {
    a: u2,
    b: i2,
};

// Find and remove the duplicate case!

comptime {
    const u: U = .{ .a = 3 };
    switch (u) {
        .{ .a = 3 } => {}, // ok!
        .{ .a = 2 },
        .{ .b = 1 },
        .{ .b = -1 },
        .{ .a = 0 },
        => @compileError("We don't want to end up here!"),
    }
}

// Since packed unions don't have the concept of an active tag, it's always legal
// to access any of their fields. This can be useful to view the same data from
// different perspectives seamlessly.
//
// Try to make the float below negative:

/// IEEE 754 half precision float
const Float = packed union(u16) {
    value: f16,
    bits: packed struct(u16) {
        mantissa: u10,
        exponent: u5,
        sign: u1,
    },
};

pub fn main() void {
    // Reminder: if the sign bit of a float is set, the number is negative!

    var number: Float = .{ .value = 2.34 };
    number.bits.??? = ???;
    if (number.value != -2.34) {
        std.debug.print("Make it negative!n", .{});
    }
}

// This concludes our introduction to packed containers. The next time you need
// control over individual bits, keep them in mind as a potent alternative!
//

const std = @import("std");

Exe: 999_the_end.zig


// 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", .{});
}