diff --git a/build.zig b/build.zig index 9209848..eaaefe6 100644 --- a/build.zig +++ b/build.zig @@ -1075,7 +1075,7 @@ const exercises = [_]Exercise{ }, .{ .main_file = "074_comptime9.zig", - .output = "My llama value is 2.", + .output = "MouseLlama joins the crew!", }, .{ .main_file = "075_quiz8.zig", diff --git a/exercises/074_comptime9.zig b/exercises/074_comptime9.zig index 4088aad..50894fd 100644 --- a/exercises/074_comptime9.zig +++ b/exercises/074_comptime9.zig @@ -1,62 +1,111 @@ -// -// In addition to knowing when to use the 'comptime' keyword, -// it's also good to know when you DON'T need it. -// -// The following contexts are already IMPLICITLY evaluated at -// compile time, and adding the 'comptime' keyword would be -// superfluous, redundant, and smelly: -// -// * The container-level scope (outside of any function in a source file) -// * Type declarations of: -// * Variables -// * Functions (types of parameters and return values) -// * Structs -// * Unions -// * Enums -// * The test expressions in inline for and while loops -// * An expression passed to the @cImport() builtin -// -// Work with Zig for a while, and you'll start to develop an -// intuition for these contexts. Let's work on that now. -// -// You have been given just one 'comptime' statement to use in -// the program below. Here it is: -// -// comptime -// -// Just one is all it takes. Use it wisely! -// const print = @import("std").debug.print; -// Being in the container-level scope, everything about this value is -// implicitly required to be known compile time. -const llama_count = 5; +// 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". -// Again, this value's type and size must be known at compile -// time, but we're letting the compiler infer both from the -// return type of a function. -const llamas = makeLlamas(llama_count); +const Animal = enum { + Mouse, + Llama, + Gator, +}; -// And here's the function. Note that the return value type -// depends on one of the input arguments! -fn makeLlamas(count: usize) [count]u8 { - var temp: [count]u8 = undefined; - var i = 0; +// 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 { - // Note that this does NOT need to be an inline 'while'. - while (i < count) : (i += 1) { - temp[i] = i; + // 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. + }; + var state = State.start; + + // 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; + var next_animal: usize = 0; + + 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' => ???, + + 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... + ??? + }, + + else => @compileError("Only llamas start with 'l'!"), + }, + } } - return temp; + 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 { - print("My llama value is {}.\n", .{llamas[2]}); + // 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!", .{}); } -// -// The lesson here is to not pepper your program with 'comptime' -// keywords unless you need them. Between the implicit compile -// time contexts and Zig's aggressive evaluation of any -// expression it can figure out at compile time, it's sometimes -// surprising how few places actually need the keyword. diff --git a/patches/patches/074_comptime9.patch b/patches/patches/074_comptime9.patch index 250d003..44a1709 100644 --- a/patches/patches/074_comptime9.patch +++ b/patches/patches/074_comptime9.patch @@ -1,11 +1,35 @@ ---- exercises/074_comptime9.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/074_comptime9.zig 2023-10-05 20:04:07.176102462 +0200 -@@ -39,7 +39,7 @@ +--- exercises/074_comptime9.zig 2026-04-11 21:35:08.132459373 -0700 ++++ answers/074_comptime9.zig 2026-04-12 07:13:37.971010827 -0700 +@@ -27,12 +27,12 @@ + 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. + }; +- var state = State.start; ++ comptime var state = State.start; - // And here's the function. Note that the return value type - // depends on one of the input arguments! --fn makeLlamas(count: usize) [count]u8 { -+fn makeLlamas(comptime count: usize) [count]u8 { - var temp: [count]u8 = undefined; - var i = 0; + // 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; +- var next_animal: usize = 0; ++ comptime var next_animal: usize = 0; + inline for (fmt) |char| { + +@@ -56,7 +56,7 @@ + // + // What do you think happens with Gators? Do they join with + // other animals or is this an error? +- 'g' => ???, ++ 'g' => @compileError("Gators refuse to join with other animals."), + + else => @compileError("No animal starts with '" ++ char ++ "'!"), + }, +@@ -68,7 +68,7 @@ + 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'!"),