diff options
Diffstat (limited to 'exercises')
43 files changed, 1680 insertions, 0 deletions
diff --git a/exercises/01_hello.zig b/exercises/01_hello.zig new file mode 100644 index 0000000..8d26940 --- /dev/null +++ b/exercises/01_hello.zig @@ -0,0 +1,22 @@ +// +// Oh no! This program is supposed to print "Hello world!" but it needs +// your help! +// +// +// Zig functions are private by default but the main() function should +// be public. +// +// A function is declared public with the "pub" statement like so: +// +// pub fn foo() void { +// ... +// } +// +// Try to fix the program and run `ziglings` to see if it works! +// +const std = @import("std"); + +fn main() void { + std.debug.print("Hello world!\n", .{}); +} + diff --git a/exercises/02_std.zig b/exercises/02_std.zig new file mode 100644 index 0000000..dcc1b87 --- /dev/null +++ b/exercises/02_std.zig @@ -0,0 +1,24 @@ +// +// Oops! This program is supposed to print a line like our Hello World +// example. But we forgot how to import the Zig Standard Library. +// +// The @import() function is built into Zig. It returns a value which +// represents the imported code. It's a good idea to store the import as +// a constant value with the same name as the import: +// +// const foo = @import("foo"); +// +// Please complete the import below: +// + +??? = @import("std"); + +pub fn main() void { + std.debug.print("Standard Library.\n", .{}); +} + +// Going deeper: imports must be declared as "constants" (with the 'const' +// keyword rather than "variables" (with the 'var' keyword) is that they +// can only be used at "compile time" rather than "run time". Zig evaluates +// const values at compile time. Don't worry if none of this makes sense +// yet! See also this answer: https://stackoverflow.com/a/62567550/695615 diff --git a/exercises/03_assignment.zig b/exercises/03_assignment.zig new file mode 100644 index 0000000..d26f2a2 --- /dev/null +++ b/exercises/03_assignment.zig @@ -0,0 +1,48 @@ +// +// 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; +// var bar: i8 = -20; +// +// Example: foo can hold 8 bits (0 to 255) +// bar can hold 16 bits (0 to 65,535) +// +// You can do just about any combination of these that you can think of: +// +// u32 can hold 0 to 4,294,967,295 +// i64 can hold −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 +// +// Please fix this program so that the types can hold the desired values +// and the errors go away! +// +const std = @import("std"); + +pub fn main() void { + const n: u8 = 50; + n = n + 5; + + const pi: u8 = 314159; + + const negative_eleven: u8 = -11; + + // There are no errors in the next line, just explanation: + // Perhaps you noticed before that the print function takes two + // parameters. Now it will make more sense: the first parameter + // is a string. The string may contain placeholders '{}', and the + // second parameter is an "anonymous list literal" (don't worry + // about this for now!) with the values to be printed. + std.debug.print("{} {} {}\n", .{n, pi, negative_eleven}); +} diff --git a/exercises/04_arrays.zig b/exercises/04_arrays.zig new file mode 100644 index 0000000..0f4ffe1 --- /dev/null +++ b/exercises/04_arrays.zig @@ -0,0 +1,51 @@ +// +// 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[3]; // 5423 +// +// Set values of an array using array[index] notation: +// +// foo[3] = 16; +// +// Get the length of an array using the len property: +// +// const length = foo.len; +// +const std = @import("std"); + +pub fn main() void { + // (Problem 1) + // This "const" is going to cause a problem later - can you see what it is? + // How do we fix it? + const some_primes = [_]u8{ 1, 3, 5, 7, 11, 13, 17, 19 }; + + // Individual values can be set with '[]' notation. + // Example: This line changes the first prime to 2 (which is correct): + some_primes[0] = 2; + + // Individual values can also be accessed with '[]' notation. + // Example: This line stores the first prime in "first": + const first = some_primes[0]; + + // (Problem 2) + // Looks like we need to complete this expression. Use the example + // above to set "fourth" to the fourth element of the some_primes array: + const fourth = some_primes[???]; + + // (Problem 3) + // Use the len property to get the length of the array: + const length = some_primes.???; + + std.debug.print("First: {}, Fourth: {}, Length: {}\n", + .{first, fourth, length}); +} diff --git a/exercises/05_arrays2.zig b/exercises/05_arrays2.zig new file mode 100644 index 0000000..9282a31 --- /dev/null +++ b/exercises/05_arrays2.zig @@ -0,0 +1,47 @@ +// +// 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 +// +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 = ???; + + // (Problem 2) + // Please set this array to using repetition. + // It should result in: 1 0 0 1 1 0 0 1 1 0 0 1 + const bit_pattern = [_]u8{ ??? } ** 3; + + // Okay, that's all of the problems. Let's see the results. + // + // We could print these arrays with leet[0], leet[1],...but let's + // have a little preview of Zig "for" loops instead: + 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", .{}); +} diff --git a/exercises/06_strings.zig b/exercises/06_strings.zig new file mode 100644 index 0000000..2430884 --- /dev/null +++ b/exercises/06_strings.zig @@ -0,0 +1,48 @@ +// +// 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 the same as: +// +// const foo = [_]u8{ 'H', 'e', 'l', 'l', 'o' }; +// +const std = @import("std"); + +pub fn main() void { + const ziggy = "stardust"; + + // (Problem 1) + // Use array square bracket syntax to get the letter 'd' from + // the string "stardust" above. + const d: u8 = ziggy[???]; + + // (Problem 2) + // Use the array repeat '**' operator to make "ha ha ha". + const laugh = "ha " ???; + + // (Problem 3) + // Use the array concatenation '++' operator to make "Major Tom". + // (You'll need to add a space as well!) + const major = "Major"; + const tom = "Tom"; + const major_tom = major ??? tom; + + // That's all the problems. Let's see our results: + std.debug.print("d={u} {}{}\n",.{d, laugh, major_tom}); + // + // Keen eyes will notice that we've put a 'u' inside the '{}' + // placeholder in the format string above. This tells the + // print() function to format the values as a UTF-8 character. + // If we didn't do this, we'd see '100', which is the decimal + // number corresponding with the 'd' character in UTF-8. + // + // 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! + // +} diff --git a/exercises/07_strings2.zig b/exercises/07_strings2.zig new file mode 100644 index 0000000..bb81bc7 --- /dev/null +++ b/exercises/07_strings2.zig @@ -0,0 +1,24 @@ +// +// 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("{}\n",.{lyrics}); +} diff --git a/exercises/08_quiz.zig b/exercises/08_quiz.zig new file mode 100644 index 0000000..e23f856 --- /dev/null +++ b/exercises/08_quiz.zig @@ -0,0 +1,34 @@ +// +// Quiz time! Let's see if you can fix this whole program. +// +// This is meant to be challenging. +// +// 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"; + + const x: u8 = 1; + + // This is something you haven't seen before: declaring an array + // without putting anything in it. There is no error here: + var lang: [3]u8 = undefined; + + // The following lines attempt to put 'Z', 'i', and 'g' into the + // 'lang' array we just created. + lang[0] = letters[x]; + + x = 3; + lang[???] = letters[x]; + + x = ???; + lang[2] = letters[???]; + + // We want to "Program in Zig!" of course: + std.debug.print("Program in {}!\n", .{lang}); +} diff --git a/exercises/09_if.zig b/exercises/09_if.zig new file mode 100644 index 0000000..28ac712 --- /dev/null +++ b/exercises/09_if.zig @@ -0,0 +1,32 @@ +// +// 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 does not equal b" +// +// The important thing about Zig's "if" is that it *only* accepts +// boolean values. It won't coerce numbers or other types of data +// to true and false. +// +const std = @import("std"); + +pub fn main() void { + const foo = 1; + + // Please fix this condition: + if (foo) { + // We want out program to print this message! + std.debug.print("Foo is 1!\n", .{}); + } else { + std.debug.print("Foo is not 1!\n", .{}); + } +} diff --git a/exercises/10_if2.zig b/exercises/10_if2.zig new file mode 100644 index 0000000..4f559cd --- /dev/null +++ b/exercises/10_if2.zig @@ -0,0 +1,25 @@ +// +// If statements are also valid expressions: +// +// var foo: u8 = if (a) 2 else 3; +// +// Note: You'll need to declare a variable type when assigning a value +// from a statement like this because the compiler isn't smart enough +// to infer the type for you. +// +// This WON'T work: +// +// var foo = if (a) 2 else 3; // error! +// +const std = @import("std"); + +pub fn main() void { + var discount = true; + + // Please use an if...else expression to set "price". + // If discount is true, the price should be $17, otherwise $20: + var price = if ???; + + std.debug.print("With the discount, the price is ${}.\n", .{price}); +} + diff --git a/exercises/11_while.zig b/exercises/11_while.zig new file mode 100644 index 0000000..4c4fc4f --- /dev/null +++ b/exercises/11_while.zig @@ -0,0 +1,34 @@ +// +// Zig 'while' statements create a loop that runs while the +// condition is true. This runs once (at most): +// +// while (condition) { +// condition = false; +// } +// +// Remember that the condition must be a boolean value and +// that we can get a boolean value from conditional operators +// such as: +// +// a == b means "a equals b" +// a < b means "a is less than b" +// a > b means "a is greater than b" +// a !=b means "a does not equal b" +// +const std = @import("std"); + +pub fn main() void { + var n: u32 = 2; + + // Please use a condition that is true UNTIL "n" reaches 1024: + while ( ??? ){ + // Print the current 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}); +} diff --git a/exercises/12_while2.zig b/exercises/12_while2.zig new file mode 100644 index 0000000..6f808c8 --- /dev/null +++ b/exercises/12_while2.zig @@ -0,0 +1,35 @@ +// +// Zig 'while' statements can have an optional 'continue expression' +// which runs every time the while loop continues (either at the +// end of the loop or when an explicit 'continue' is invoked (we'll +// try those out next): +// +// while (condition) : (continue expression) +// ... +// } +// +// Example: +// +// var foo = 2; +// while (foo<10) : (foo+=2) +// // Do something with even numbers less than 10... +// } +// +// See if you can re-write the last exercise using a continue +// expression: +// +const std = @import("std"); + +pub fn main() void { + var n: u32 = 2; + + // Please set the continue expression so that we get the desired + // results in the print statement below. + while (n < 1000) : ??? { + // Print the current number + std.debug.print("{} ", .{n}); + } + + // As in the last exercise, we want this to result in "n=1024" + std.debug.print("n={}\n", .{n}); +} diff --git a/exercises/13_while3.zig b/exercises/13_while3.zig new file mode 100644 index 0000000..3ff42ff --- /dev/null +++ b/exercises/13_while3.zig @@ -0,0 +1,33 @@ +// +// The last two exercises were functionally identical. Continue +// expressions really show their utility when used with 'continue' +// statements! +// +// Example: +// +// while (condition) : (continue expression){ +// +// if(other condition) continue; +// +// } +// +// The "continue expression" executes every time the loop restarts +// whether the "continue" statement happens or not. +// +const std = @import("std"); + +pub fn main() void { + var n: u32 = 1; + + // I want to print every number between 1 and 20 that is NOT + // divisible by 3 or 5. + while (n <= 20) : (n+=1) { + // The '%' symbol is the "modulo" operator and it + // returns the remainder after division. + if(n % 3 == 0) ???; + if(n % 5 == 0) ???; + std.debug.print("{} ", .{n}); + } + + std.debug.print("\n", .{}); +} diff --git a/exercises/14_while4.zig b/exercises/14_while4.zig new file mode 100644 index 0000000..a28b9a9 --- /dev/null +++ b/exercises/14_while4.zig @@ -0,0 +1,26 @@ +// +// You can force a loop to exit immediately with a "break" statement: +// +// while (condition) : (continue expression){ +// +// if(other condition) break; +// +// } +// +// Continue expressions do NOT execute when a while loop stops +// because of a break! +// +const std = @import("std"); + +pub fn main() void { + var n: u32 = 1; + + // Oh dear! This while loop will go forever!? + // Please fix this so the print statement below gives the desired output. + while (true) : (n+=1) { + if(???) ???; + } + + // Result: we want n=4 + std.debug.print("n={}\n", .{n}); +} diff --git a/exercises/15_for.zig b/exercises/15_for.zig new file mode 100644 index 0000000..652478b --- /dev/null +++ b/exercises/15_for.zig @@ -0,0 +1,28 @@ +// +// Behold the 'for' loop! It lets you execute code for each +// member of an array: +// +// for (items) |item| { +// +// // Do something with item +// +// } +// +const std = @import("std"); + +pub fn main() void { + const story = [_]u8{ 'h', 'h', 's', 'n', 'h' }; + + std.debug.print("A Dramatic Story: ", .{}); + + for (???) |???| { + if(scene == 'h') std.debug.print(":-) ", .{}); + if(scene == 's') std.debug.print(":-( ", .{}); + if(scene == 'n') std.debug.print(":-| ", .{}); + } + + std.debug.print("The End.\n", .{}); +} +// +// Note that "for" loops also work on things called "slices" +// which we'll see later. diff --git a/exercises/16_for2.zig b/exercises/16_for2.zig new file mode 100644 index 0000000..0a62a1a --- /dev/null +++ b/exercises/16_for2.zig @@ -0,0 +1,33 @@ +// +// For loops also let you store the "index" of the iteration - a +// number starting with 0 that counts up with each iteration: +// +// for (items) |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 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 piece: + for (bits) |bit, ???| { + var place_value = std.math.pow(u32, 2, @intCast(u32, i)); + value += place_value * bit; + } + + std.debug.print("The value of bits '1101': {}.\n", .{value}); +} diff --git a/exercises/17_quiz2.zig b/exercises/17_quiz2.zig new file mode 100644 index 0000000..339f733 --- /dev/null +++ b/exercises/17_quiz2.zig @@ -0,0 +1,28 @@ +// +// 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's +// some problems. :-( +// +const std = import standard library; + +function main() void { + var i: u8 = 1; + var stop_at: u8 = 16; + + // What kind of loop is this? A 'for' or a 'while'? + ??? (i <= stop_at) : (i += 1) { + if (i % 3 == 0) std.debug.print("Fizz", .{}); + if (i % 5 == 0) std.debug.print("Buzz", .{}); + if ( !(i % 3 == 0) and !(i % 5 == 0) ) { + std.debug.print("{}", .{???}); + } + std.debug.print(", ", .{}); + } + std.debug.print("\n",.{}); +} diff --git a/exercises/18_functions.zig b/exercises/18_functions.zig new file mode 100644 index 0000000..bda90cd --- /dev/null +++ b/exercises/18_functions.zig @@ -0,0 +1,34 @@ +// +// Functions! We've already seen a lot of one called "main()". Now let's try +// writing one of our own: +// +// 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. +// +// If your function doesn't take any parameters and doesn't return anything, +// it would be defined like main(): +// +// fn foo() void { } +// +const std = @import("std"); + +pub fn main() void { + // The new function deepThought() should return the number 42. See below. + const answer: u8 = deepThought(); + + std.debug.print("Answer to the Ultimate Question: {}\n", .{answer}); +} + +// +// Please define the deepThought() function below. +// +// We're just missing a couple things. One thing we're NOT missing is the +// keyword "pub", which is not needed here. Can you guess why? +// +??? deepThought() ??? { + return 42; // Number courtesy Douglas Adams +} diff --git a/exercises/19_functions2.zig b/exercises/19_functions2.zig new file mode 100644 index 0000000..4d195a7 --- /dev/null +++ b/exercises/19_functions2.zig @@ -0,0 +1,31 @@ +// +// 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 an other types ("name": "type"): +// +// fn myFunction( number: u8, is_lucky: bool ) { +// ... +// } +// +const std = @import( "std" ); + +pub fn main() void { + std.debug.print("Powers of two: {} {} {} {}\n", .{ + twoToThe(1), + twoToThe(2), + twoToThe(3), + twoToThe(4), + }); +} + +// +// Please give this function the correct input parameter(s). +// You'll need to figure out the parameter name and type that we're +// expecting. The output type has already been specified for you. +// +fn twoToThe(???) u32 { + return std.math.pow(u32, 2, my_number); + // std.math.pow(type, a, b) takes a numeric type and two numbers + // of that type and returns "a to the power of b" as that same + // numeric type. +} diff --git a/exercises/20_quiz3.zig b/exercises/20_quiz3.zig new file mode 100644 index 0000000..e18ef37 --- /dev/null +++ b/exercises/20_quiz3.zig @@ -0,0 +1,45 @@ +// +// Let's see if we can make use of some of things we've learned so far. +// We'll create two functions: one that contains a "for" loop and one +// that contains a "while" loop. +// +// Both of these are simply labeled "loop" below. +// +const std = @import( "std" ); + +pub fn main() void { + const my_numbers = [4]u16{ 5,6,7,8 }; + + printPowersOfTwo(my_numbers); + std.debug.print("\n", .{}); +} + +// +// You won't see this every day: a function that takes an array with +// exactly four u16 numbers. This is not how you would normally pass +// an array to a function. We'll learn about slices and pointers in +// a little while. For now, we're using what we know. +// +// This function prints, but does not return anything. +// +fn printPowersOfTwo(numbers: [4]u16) ??? { + loop (numbers) |n| { + std.debug.print("{} ", .{twoToThe(n)}); + } +} + +// +// This function bears a striking resemblance to twoToThe() in the last +// exercise. But don't be fooled! This one does the math without the aid +// of the standard library! +// +fn twoToThe(number: u16) ??? { + var n: u16 = 0; + var total: u16 = 1; + + loop (n < number) : (n += 1) { + total *= 2; + } + + return ???; +} diff --git a/exercises/21_errors.zig b/exercises/21_errors.zig new file mode 100644 index 0000000..34c5e18 --- /dev/null +++ b/exercises/21_errors.zig @@ -0,0 +1,46 @@ +// +// Believe it or not, sometimes things to wrong in programs. +// +// In Zig, an error is a value. Errors are named so we can identify +// things that can go wrong. Errors are created in "error sets", which +// are just a collection of named errors. +// +// We have the start of an error set, but we're missing the condition +// "TooSmall". Please add it where needed! +const MyNumberError = error{ + TooBig, + ???, + TooFour, +}; + +const std = @import("std"); + +pub fn main() void { + var nums = [_]u8{2,3,4,5,6}; + + for (nums) |n| { + std.debug.print("{}", .{n}); + + const number_error = numberFail(n); + + if (number_error == MyNumberError.TooBig) { + std.debug.print(">4. ", .{}); + } + if (???) { + std.debug.print("<4. ", .{}); + } + if (number_error == MyNumberError.TooFour) { + std.debug.print("=4. ", .{}); + } + } + + std.debug.print("\n", .{}); +} + +// Notice how this function can return any member of the MyNumberError +// error set. +fn numberFail(n: u8) MyNumberError { + if(n > 4) return MyNumberError.TooBig; + if(n < 4) return MyNumberError.TooSmall; // <---- this one is free! + return MyNumberError.TooFour; +} diff --git a/exercises/22_errors2.zig b/exercises/22_errors2.zig new file mode 100644 index 0000000..fcfd391 --- /dev/null +++ b/exercises/22_errors2.zig @@ -0,0 +1,30 @@ +// +// 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 let's us make what's called an "error union" which is a value +// which could either be a regular value OR an error from a set: +// +// var text: MyErrorSet!Text = getText('foo.txt'); +// +// For now, let's just see if we can try making an error union! +// +const std = @import("std"); + +const MyNumberError = error{ TooSmall }; + +pub fn main() void { + var my_number: ??? = 5; + + // Looks like my_number will need to either store a number OR + // an error. Can you set the type correctly above? + my_number = MyNumberError.TooSmall; + + std.debug.print("I compiled!", .{}); +} + diff --git a/exercises/23_errors3.zig b/exercises/23_errors3.zig new file mode 100644 index 0000000..6060bf1 --- /dev/null +++ b/exercises/23_errors3.zig @@ -0,0 +1,28 @@ +// +// 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 { + var a: u32 = addTwenty(44) catch 22; + var b: u32 = addTwenty(4) ??? 22; + + std.debug.print("a={}, b={}", .{a,b}); +} + +// Please provide the return type from this function. +// Hint: it'll be an error union. +fn addTwenty(n: u32) ??? { + if (n < 5) { + return MyNumberError.TooSmall; + } else { + return n + 20; + } +} diff --git a/exercises/24_errors4.zig b/exercises/24_errors4.zig new file mode 100644 index 0000000..b60cc2d --- /dev/null +++ b/exercises/24_errors4.zig @@ -0,0 +1,68 @@ +// +// 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 just our way of dealing with the fact + // that makeJustRight() returns a error union (for now). + var a: u32 = makeJustRight(44) catch 0; + var b: u32 = makeJustRight(14) catch 0; + var c: u32 = makeJustRight(4) catch 0; + + std.debug.print("a={}, b={}, c={}", .{a,b,c}); +} + +// In this silly example we've split the responsibility of making +// a number just right into four (!) functions: +// +// makeJustRight() Calls fixTooBig(), cannot fix any errors. +// fixTooBig() Calls fixTooSmall(), fixes TooBig errors. +// fixTooSmall() Calls detectProblems(), fixes TooSmall errors. +// detectProblems() Returns the number or an error. +// +fn makeJustRight(n: u32) MyNumberError!u32 { + return fixTooBig(n) catch |err| { return err; }; +} + +fn fixTooBig(n: u32) MyNumberError!u32 { + return fixTooSmall(n) catch |err| { + if (err == MyNumberError.TooBig) { + return 20; + } + + return err; + }; +} + +fn fixTooSmall(n: u32) MyNumberError!u32 { + // Oh dear, this is missing a lot! But don't worry, it's nearly + // identical to fixTooBig() above. + // + // If we get a TooSmall error, we should return 10. + // If we get any other error, we should return that error. + // Otherwise, we return the u32 number. + return detectProblems(n) ??? +} + +fn detectProblems(n: u32) MyNumberError!u32 { + if (n < 10) return MyNumberError.TooSmall; + if (n > 20) return MyNumberError.TooBig; + return n; +} + diff --git a/exercises/25_errors5.zig b/exercises/25_errors5.zig new file mode 100644 index 0000000..d9e9ce1 --- /dev/null +++ b/exercises/25_errors5.zig @@ -0,0 +1,40 @@ +// +// 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 { + var a: u32 = addFive(44) catch 0; + var b: u32 = addFive(14) catch 0; + var c: u32 = addFive(4) catch 0; + + std.debug.print("a={}, b={}, c={}", .{a,b,c}); +} + +fn addFive(n: u32) MyNumberError!u32 { + // + // This function needs to return any error which might come back from fix(). + // Please use a "try" statement rather than a "catch". + // + var x = detect(n); + + return x + 5; +} + +fn detect(n: u32) MyNumberError!u32 { + if (n < 10) return MyNumberError.TooSmall; + if (n > 20) return MyNumberError.TooBig; + return n; +} + diff --git a/exercises/26_hello2.zig b/exercises/26_hello2.zig new file mode 100644 index 0000000..237d27c --- /dev/null +++ b/exercises/26_hello2.zig @@ -0,0 +1,23 @@ +// +// 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 have consequences elsewhere. +pub fn main() !void { + + // We get a Writer for Standard Out so we can print() to it. + const stdout = std.io.getStdOut().writer(); + + // Unlike std.debug.print(), the Standard Out writer can fail + // with an error. We don't care _what_ the error is, we want + // to be able to pass it up as a return value of main(). + // + // We just learned of a single statement which can accomplish this. + stdout.print("Hello world!\n", .{}); +} diff --git a/exercises/27_defer.zig b/exercises/27_defer.zig new file mode 100644 index 0000000..b41e2af --- /dev/null +++ b/exercises/27_defer.zig @@ -0,0 +1,25 @@ +// +// You can assign some code to run _after_ a block of code exits by +// deferring it with a "defer" statement: +// +// { +// defer runLater(); +// runNow(); +// } +// +// In the example above, runLater() will run when the block ({...}) +// is finished. So the code above will run in the following order: +// +// runNow(); +// runLater(); +// +// This feature seems strange at first, but we'll see how it could be +// useful in the next exercise. +const std = @import("std"); + +pub fn main() void { + // Without changing anything else, please add a 'defer' statement + // to this code so that our program prints "One Two\n": + std.debug.print("Two\n", .{}); + std.debug.print("One ", .{}); +} diff --git a/exercises/28_defer2.zig b/exercises/28_defer2.zig new file mode 100644 index 0000000..5c991da --- /dev/null +++ b/exercises/28_defer2.zig @@ -0,0 +1,29 @@ +// +// Now that you know how "defer" works, let's do something more +// interesting with it. +// +const std = @import("std"); + +pub fn main() void { + const animals = [_]u8{ 'g', 'c', 'd', 'd', 'g', 'z' }; + + for (animals) |a| printAnimal(a); + + + std.debug.print("done.\n", .{}); +} + +// This function is _supposed_ to print an animal name in parentheses +// like "(Goat) ", but we somehow need to print the end parenthesis +// even though this function can return in four different places! +fn printAnimal(animal: u8) void { + std.debug.print("(", .{}); + + std.debug.print(") ", .{}); // <---- how!? + + if (animal == 'g'){ std.debug.print("Goat", .{}); return; } + if (animal == 'c'){ std.debug.print("Cat", .{}); return; } + if (animal == 'd'){ std.debug.print("Dog", .{}); return; } + + std.debug.print("Unknown", .{}); +} diff --git a/exercises/29_errdefer.zig b/exercises/29_errdefer.zig new file mode 100644 index 0000000..cd2158d --- /dev/null +++ b/exercises/29_errdefer.zig @@ -0,0 +1,60 @@ +// +// Another common problem is a block of code that could exit in multiple +// places due to an error - but that needs to run 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: + var a: u32 = makeNumber() catch return; + var b: u32 = makeNumber() catch return; + + std.debug.print("Numbers: {}, {}\n", .{a,b}); +} + +fn makeNumber() MyErr!u32 { + std.debug.print("Getting number...", .{}); + + // Please make the "failed" message print ONLY if the makeNumber() + // function exits with an error: + std.debug.print("failed!\n", .{}); + + var num = try getNumber(); // <-- This could fail! + + num = try increaseNumber(num); // <-- This could ALSO fail! + + std.debug.print("got {}. ", .{num}); + + return num; +} + +fn getNumber() MyErr!u32 { + // I _could_ fail...but I don't! + return 4; +} + +fn increaseNumber(n: u32) MyErr!u32 { + // I fail after the first time you run me! + if (counter > 0) return MyErr.IncFail; + + // Sneaky, weird global stuff. + counter += 1; + + return n + 1; +} diff --git a/exercises/30_switch.zig b/exercises/30_switch.zig new file mode 100644 index 0000000..b10ad14 --- /dev/null +++ b/exercises/30_switch.zig @@ -0,0 +1,55 @@ +// +// The "switch" statement lets you match the possible values of an +// expression and perform a different action for each. +// +// This switch: +// +// switch (players) { +// 1 => startOnePlayerGame(), +// 2 => startTwoPlayerGame(), +// else => { +// alert(); +// return GameError.TooManyPlayers; +// } +// } +// +// Is equivalent to this if/else: +// +// if (players == 1) startOnePlayerGame(); +// else if (players == 2) startTwoPlayerGame(); +// else { +// alert(); +// return GameError.TooManyPlayers; +// } +// +// +// +const std = @import("std"); + +pub fn main() void { + const lang_chars = [_]u8{ 26, 9, 7, 42 }; + + for (lang_chars) |c| { + switch (c) { + 1 => std.debug.print("A", .{}), + 2 => std.debug.print("B", .{}), + 3 => std.debug.print("C", .{}), + 4 => std.debug.print("D", .{}), + 5 => std.debug.print("E", .{}), + 6 => std.debug.print("F", .{}), + 7 => std.debug.print("G", .{}), + 8 => std.debug.print("H", .{}), + 9 => std.debug.print("I", .{}), + 10 => std.debug.print("J", .{}), + // ... we don't need everything in between ... + 25 => std.debug.print("Y", .{}), + 26 => std.debug.print("Z", .{}), + // Switch statements must be "exhaustive" (there must be a + // match for every possible value). Please add an "else" + // to this switch to print a question mark "?" when c is + // not one of the existing matches. + } + } + + std.debug.print("\n", .{}); +} diff --git a/exercises/31_switch2.zig b/exercises/31_switch2.zig new file mode 100644 index 0000000..138b809 --- /dev/null +++ b/exercises/31_switch2.zig @@ -0,0 +1,42 @@ +// +// What's really nice is that you can use a switch statement as an +// expression to return a value. +// +// var 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| { + var real_char: u8 = switch (c) { + 1 => 'A', + 2 => 'B', + 3 => 'C', + 4 => 'D', + 5 => 'E', + 6 => 'F', + 7 => 'G', + 8 => 'H', + 9 => 'I', + 10 => 'J', + // ... + 25 => 'Y', + 26 => 'Z', + // As in the last exercise, please add the "else" clause + // and this time, have it return an exclamation mark "!". + }; + + std.debug.print("{c}", .{real_char}); + // Note: "{c}" forces print() to display the value as a character. + // Can you guess what happens if you remove the "c"? Try it! + } + + std.debug.print("\n", .{}); +} diff --git a/exercises/32_unreachable.zig b/exercises/32_unreachable.zig new file mode 100644 index 0000000..c81efac --- /dev/null +++ b/exercises/32_unreachable.zig @@ -0,0 +1,38 @@ +// +// Zig has an "unreachable" statement. Use it when you want to tell the +// compiler that a branch of code should never be executed and that the +// mere act of reaching it is an error. +// +// if (true) { +// ... +// } else { +// unreachable; +// } +// +// Here we've made a little virtual machine that performs mathematical +// operations on a single numeric value. It looks great but there's one +// little problem: the switch statement doesn't cover every possible +// value of a u8 number! +// +// WE know there are only three operations but Zig doesn't. Use the +// unreachable statement to make the switch complete. Or ELSE. :-) +// +const std = @import("std"); + +pub fn main() void { + const operations = [_]u8{ 1, 1, 1, 3, 2, 2 }; + + var current_value: u32 = 0; + + for (operations) |op| { + switch (op) { + 1 => { current_value += 1; }, + 2 => { current_value -= 1; }, + 3 => { current_value *= current_value; }, + } + + std.debug.print("{} ", .{current_value}); + } + + std.debug.print("\n", .{}); +} diff --git a/exercises/33_iferror.zig b/exercises/33_iferror.zig new file mode 100644 index 0000000..ed92e94 --- /dev/null +++ b/exercises/33_iferror.zig @@ -0,0 +1,49 @@ +// +// Let's revisit the very first error exercise. This time, we're going to +// look at a special error-handling type 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. +// +const MyNumberError = error{ + TooBig, + TooSmall, +}; + +const std = @import("std"); + +pub fn main() void { + var nums = [_]u8{2,3,4,5,6}; + + for (nums) |num| { + std.debug.print("{}", .{num}); + + var n = numberMaybeFail(num); + if (n) |value| { + std.debug.print("=4. ", .{}); + } else |err| switch (err) { + MyNumberError.TooBig => std.debug.print(">4. ", .{}), + // Please add a match for TooSmall here and have it print: "<4. " + } + } + + std.debug.print("\n", .{}); +} + +// This time we'll have numberMaybeFail() return an error union rather +// than a straight error. +fn numberMaybeFail(n: u8) MyNumberError!u8 { + if(n > 4) return MyNumberError.TooBig; + if(n < 4) return MyNumberError.TooSmall; + return n; +} diff --git a/exercises/34_quiz4.zig b/exercises/34_quiz4.zig new file mode 100644 index 0000000..43734b7 --- /dev/null +++ b/exercises/34_quiz4.zig @@ -0,0 +1,24 @@ +// +// Quiz time. See if you can make this program work! +// +// Solve this any way you like, just be sure the output is: +// +// my_num=42 +// +const std = @import("std"); + +const NumError = error{ IllegalNumber }; + +pub fn main() void { + const stdout = std.io.getStdOut().writer(); + + const my_num: u32 = getNumber(); + + try stdout.print("my_num={}\n", .{my_num}); +} + +// Just don't modify this function. It's "perfect" the way it is. :-) +fn getNumber() NumError!u32 { + if( false ) return NumError.IllegalNumber; + return 42; +} diff --git a/exercises/35_enums.zig b/exercises/35_enums.zig new file mode 100644 index 0000000..cf455a4 --- /dev/null +++ b/exercises/35_enums.zig @@ -0,0 +1,49 @@ +// +// Remember that little mathematical virtual machine we made using the +// "unreachable" statement? Well, there were two problems with the +// way we were using op codes: +// +// 1. Having to remember op codes by number is no good. +// 2. We had to use "unreachable" because Zig had no way of knowing +// how many valid op codes there were. +// +// An "enum" is a Zig construct that lets you give names to numeric +// values and store them in a set. They look a lot like error sets: +// +// const Fruit = enum{ apple, pear, orange }; +// +// const my_fruit = Fruit.apple; +// +// Let's use an enum in place of the numbers we were using in the +// previous version! +// +const std = @import("std"); + +// Please complete the enum! +const Ops = enum{ ??? }; + +pub fn main() void { + const operations = [_]Ops{ + Ops.inc, + Ops.inc, + Ops.inc, + Ops.pow, + Ops.dec, + Ops.dec + }; + + var current_value: u32 = 0; + + for (operations) |op| { + switch (op) { + Ops.inc => { current_value += 1; }, + Ops.dec => { current_value -= 1; }, + Ops.pow => { current_value *= current_value; }, + // No "else" needed! Why is that? + } + + std.debug.print("{} ", .{current_value}); + } + + std.debug.print("\n", .{}); +} diff --git a/exercises/36_enums2.zig b/exercises/36_enums2.zig new file mode 100644 index 0000000..2e04415 --- /dev/null +++ b/exercises/36_enums2.zig @@ -0,0 +1,61 @@ +// +// 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 built-in function: +// +// var my_stuff: u8 = @enumToInt(Stuff.foo); +// +// Note how that built-in function starts with "@" just like the +// @import() function we've been using. +// +const std = @import("std"); + +// Zig lets us write integers in hexadecimal format: +// +// 0xf (is the value 15 in hex) +// +// Web browsers let us specify colors using a hexadecimal +// number where each byte represents the brightness of the +// Red, Green, or Blue component (RGB) where two hex digits +// are one byte with a value range of 0-255: +// +// #RRGGBB +// +// Please define and use a pure blue value Color: +const Color = enum(u32){ + red = 0xff0000, + green = 0x00ff00, + blue = ???, +}; + +pub fn main() void { + // Remeber Zig's multi-line strings? Here they are again. + // Also, check out this cool format string: + // + // {x:0>6} + // ^ + // x type ('x' is lower-case hexadecimal) + // : separator (needed for format syntax) + // 0 padding character (default is ' ') + // > alignment ('>' aligns right) + // 6 width (use padding to force width) + // + // Please add this formatting to the blue value. + // (Even better, experiment without it, or try parts of it + // to see what prints!) + std.debug.print( + \\<p> + \\ <span style="color: #{x:0>6}">Red</span> + \\ <span style="color: #{x:0>6}">Green</span> + \\ <span style="color: #{}">Blue</span> + \\</p> + , .{ + @enumToInt(Color.red), + @enumToInt(Color.green), + @enumToInt(???), // Oops! We're missing something! + }); +} diff --git a/exercises/37_structs.zig b/exercises/37_structs.zig new file mode 100644 index 0000000..dd4b633 --- /dev/null +++ b/exercises/37_structs.zig @@ -0,0 +1,59 @@ +// +// 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, .y=27 }; +// point2 = Point{ .x=7, .y=13, .y=34 }; +// +// The Point above is an example of a "struct" (short for "structure"). +// Here's how it 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 class. +const Class = 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{ + class: Class, + gold: u32, + experience: u32, +}; + +pub fn main() void { + // Please initialize Glorp with 100 health. + var glorp_the_wise = Character{ + .class = Class.wizard, + .gold = 20, + .experience = 10, + }; + + // Glorp gains some gold. + glorp_the_wise.gold += 5; + + // Ouch! Glorp takes a punch! + glorp_the_wise.health -= 10; + + std.debug.print("Your wizard has {} health and {} gold.", .{ + glorp_the_wise.health, + glorp_the_wise.gold + }); +} diff --git a/exercises/38_structs2.zig b/exercises/38_structs2.zig new file mode 100644 index 0000000..b6def93 --- /dev/null +++ b/exercises/38_structs2.zig @@ -0,0 +1,51 @@ +// +// 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 all (both) using a loop. +// +const std = @import("std"); + +const Class = enum{ + wizard, + thief, + bard, + warrior, +}; + +const Character = struct{ + class: Class, + gold: u32, + health: u8, + experience: u32, +}; + +pub fn main() void { + var chars: [2]Character = undefined; + + // Glorp the Wise + chars[0] = Character{ + .class = Class.wizard, + .gold = 20, + .health = 100, + .experience = 10, + }; + + // Please add "Zump the Loud" with the following properties: + // + // class 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) |c, num| { + std.debug.print("Character {} - G:{} H:{} XP:{}\n", + .{num+1, c.gold, c.health, c.experience}); + } +} diff --git a/exercises/39_pointers.zig b/exercises/39_pointers.zig new file mode 100644 index 0000000..25b56c6 --- /dev/null +++ b/exercises/39_pointers.zig @@ -0,0 +1,36 @@ +// +// 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 current 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; + var num1_pointer: *u8 = &num1; + + var num2: u8 = undefined; + + // Please make num2 equal 5 using num1_pointer! + // (See the "cheatsheet" above for ideas.) + num2 = ???; + + std.debug.print("num1: {}, num2: {}\n", .{num1, num2}); +} diff --git a/exercises/40_pointers2.zig b/exercises/40_pointers2.zig new file mode 100644 index 0000000..b046dc1 --- /dev/null +++ b/exercises/40_pointers2.zig @@ -0,0 +1,27 @@ +// +// 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 constant pointer to a variable, but you cannot +// make a variable pointer to a constant. 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. It's a safety thing (to prevent mistakes). +// +const std = @import("std"); + +pub fn main() void { + const a: u8 = 12; + const b: *u8 = &a; // fix this! + + std.debug.print("a: {}, b: {}\n", .{a, b.*}); +} diff --git a/exercises/41_pointers3.zig b/exercises/41_pointers3.zig new file mode 100644 index 0000000..21a43bd --- /dev/null +++ b/exercises/41_pointers3.zig @@ -0,0 +1,41 @@ +// +// The tricky part is that the pointer's mutability (var vs const) refers +// to the ability to change what the pointer POINTS TO, not the ability +// to change the VALUE at that location! +// +// const locked: u8 = 5; +// var unlocked: u8 = 10; +// +// const p1: *const u8 = &locked; +// var p2: *const u8 = &locked; +// +// Both p1 and p2 point to constant values which cannot change. However, +// p2 can be changed to point to something else and p1 cannot! +// +// const p3: *u8 = &unlocked; +// var p4: *u8 = &unlocked; +// const p5: *const u8 = &unlocked; +// var p6: *const u8 = &unlocked; +// +// Here p3 and p4 can both be used to change the value they point to but +// p3 cannot point at anything else. +// What's interesting is that p5 and p6 act like p1 and p2, but point to +// the value at "unlocked". This is what we mean when we say that we can +// make a constant reference to any value! +// +const std = @import("std"); + +pub fn main() void { + var foo: u8 = 5; + var bar: u8 = 10; + + // Please define pointer "p" so that it can point to EITHER foo or + // bar AND change the value it points to! + ??? p: ??? = undefined; + + p = &foo; + p.* += 1; + p = &bar; + p.* += 1; + std.debug.print("foo={}, bar={}\n", .{foo, bar}); +} diff --git a/exercises/42_pointers4.zig b/exercises/42_pointers4.zig new file mode 100644 index 0000000..e6b8964 --- /dev/null +++ b/exercises/42_pointers4.zig @@ -0,0 +1,33 @@ +// +// Now let's use pointers to do something we haven't been +// able to do before: pass a value by reference to a function! +// +const std = @import("std"); + +pub fn main() void { + var num: u8 = 1; + var more_nums = [_]u8{ 1, 1, 1, 1 }; + + // Let's pass a reference to num to our function and print it: + makeFive(&num); + std.debug.print("num: {}, ", .{num}); + + + // Now something interesting. Let's pass a reference to a + // specific array value: + makeFive(&more_nums[2]); + + // And print the array: + std.debug.print("more_nums: ", .{}); + for (more_nums) |n| { + std.debug.print("{} ", .{n}); + } + + std.debug.print("\n", .{}); +} + +// This function should take a reference to a u8 value and set it +// to 5. +fn makeFive(x: *u8) void { + ??? = 5; // fix me! +} diff --git a/exercises/43_pointers5.zig b/exercises/43_pointers5.zig new file mode 100644 index 0000000..adfaea1 --- /dev/null +++ b/exercises/43_pointers5.zig @@ -0,0 +1,84 @@ +// +// Passing integer pointers around is generally not something you're going +// to do. Integers are cheap to copy. +// +// But you know what IS useful? Pointers to structs: +// +// 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 pointer arguments: +// +// fn foo(v: *Vertex) void { +// v.x += 2; +// v.y += 3; +// v.z += 7; +// } +// +// And pass references to them: +// +// foo(&v1); +// +// +// Let's revisit our RPG example and make a printCharacter() function +// that takes a Character pointer. +// +const std = @import("std"); + +const Class = enum{ + wizard, + thief, + bard, + warrior, +}; + +const Character = struct{ + class: Class, + gold: u32, + health: u8, + experience: u32, +}; + +pub fn main() void { + var glorp = Character{ + .class = Class.wizard, + .gold = 10, + .health = 100, + .experience = 20, + }; + + // FIX ME! + // Please pass our Character "glorp" to printCharacter(): + printCharacter( ??? ); +} + + +// Note how this function's "c" parameter is a pointer to a Character struct. +fn printCharacter(c: *Character) void { + + // Here's something you haven't seen before: when switching an enum, you + // don't have to write the full enum name. Zig understands that ".wizard" + // means "Class.wizard" when we switch on a Class enum value: + const class_name = switch (c.class) { + .wizard => "Wizard", + .thief => "Thief", + .bard => "Bard", + .warrior => "Warrior", + }; + + std.debug.print("{s} (G:{} H:{} XP:{})", .{ + class_name, + c.gold, + c.health, + c.experience, + }); +} |