diff options
-rw-r--r-- | build.zig | 5 | ||||
-rw-r--r-- | exercises/51_values.zig | 180 | ||||
-rw-r--r-- | patches/patches/51_values.patch | 12 |
3 files changed, 196 insertions, 1 deletions
@@ -264,7 +264,10 @@ const exercises = [_]Exercise{ .main_file = "50_no_value.zig", .output = "That is not dead which can eternal lie / And with strange aeons even death may die.", }, - // 51 pass-by-value and const fn params + .{ + .main_file = "51_values.zig", + .output = "1:false!. 2:true!. 3:true!. XP before:0, after:200.", + }, // 52 slices! }; diff --git a/exercises/51_values.zig b/exercises/51_values.zig new file mode 100644 index 0000000..3038ade --- /dev/null +++ b/exercises/51_values.zig @@ -0,0 +1,180 @@ +// +// 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. +// +// (Conversational) Ah, that's better. Now we can look at some +// familiar Zig code! +// +// @import() adds the imported code to your own. In this case, +// compiled code from the standard library is compiled in with your +// program. It is loaded into RAM with the code you write when it +// runs. And that thing we give a const name? That's a struct! + +const std = @import("std"); + +// Remember our old RPG Character struct? A struct is really just a +// very convenient way to deal with memory. These fields (gold, +// health, experience) are all values of a particular size. Add them +// together and you have the size of the struct as a whole. + +const Character = struct { + gold: u32 = 0, + health: u8 = 100, + experience: u32 = 0, +}; + +// Here we create a character called "the_narrator" that is a constant +// (immutable) instance of a Character struct. It is stored in your +// program as data, and like the instruction code, it is loaded into +// RAM when your program runs. The relative location of this data in +// memory is hard-coded and neither the address nor the value changes. + +const the_narrator = Character{ + .gold = 12, + .health = 99, + .experience = 9000, +}; + +// This "global_wizard" character is very similar. The address for +// this data won't change, but the data itself can since this is a var +// and not a const. + +var global_wizard = Character{}; + +// A function is instruction code at a particular address. Function +// parameters in Zig are always immutable. They are stored in "the +// stack". A stack is a type of data structure and "the stack" is a +// specific bit of RAM reserved for your program. The CPU has special +// support for adding and removing things from "the stack", so it is +// an extremely efficient place for memory storage. +// +// Also, when a function executes, the input arguments are often +// loaded into the beating heart of the CPU itself in registers. +// +// Our main() function here has no input parameters, but it will have +// a stack entry (called a "frame"). + +pub fn main() void { + + // Here, the "glorp" character will be allocated on the stack + // because each instance of glorp is mutable and therefore unique + // to the invocation of this function. + + var glorp = Character{ + .gold = 30, + }; + + // However, this "skull_farmer" character will be put in the + // global immutable data even though it's defined in a function. + // Since it's immutable, all invocations of the function can share + // this one value. + + const skull_farmer = Character{}; + + // The "reward_xp" value is interesting. It's a constant value, so + // it could go with other global data. 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. The + // "debug" field refers to another struct. And "print" is a public + // function namespaced within THAT struct. + // + // Let's assign the std.debug.print function to a const named + // "print" so that we can use this new name later! + + const print = ???; + + // Now let's look at assigning and pointing to values in Zig. + // + // We'll try three different ways of making a new name to access + // our glorp Character and change one of its values. + // + // "glorp_access1" is incorrectly named! We asked Zig to set aside + // memory for another Character struct. So when we assign glorp to + // glorp_access1 here, we're actually assigning all of the fields + // to make a copy! Now we have two separate characters. + // + // You don't need to fix this. But notice what gets printed in + // your program's output for this one compared to the other two + // assignments below! + + var glorp_access1: Character = glorp; + glorp_access1.gold = 111; + print("1:{}!. ", .{glorp.gold == glorp_access1.gold}); + + // NOTE: + // + // If we tried to do this with a const Character instead of a + // var, changing the gold field would give us a compiler error + // because const values are immutable! + // + // "glorp_access2" will do what we want. It points to the original + // glorp's address. Also remember that we get one implicit + // dereference with struct fields, so accessing the "gold" field + // from glorp_access2 looks just like accessing it from glorp + // itself. + + var glorp_access2: *Character = &glorp; + glorp_access2.gold = 222; + print("2:{}!. ", .{glorp.gold == glorp_access2.gold}); + + // "glorp_access3" is interesting. It's also a pointer, but it's a + // const. Won't that disallow changing the gold value? No! As you + // may recall from our earlier pointer experiments, a constant + // pointer can't change what it's pointing AT, but the value at + // the address it points to is still mutable! So we CAN change it. + + const glorp_access3: *Character = &glorp; + glorp_access3.gold = 333; + print("3:{}!. ", .{glorp.gold == glorp_access3.gold}); + + // Passing arguments to functions is pretty much exactly like + // making an assignment to a const (since Zig enforces that ALL + // function parameters are const). + // + // Knowing that, 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}); + + levelUp(glorp, reward_xp); + + print("after:{}.\n", .{glorp.experience}); +} + +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! diff --git a/patches/patches/51_values.patch b/patches/patches/51_values.patch new file mode 100644 index 0000000..43d1f65 --- /dev/null +++ b/patches/patches/51_values.patch @@ -0,0 +1,12 @@ +96c96 +< const print = ???; +--- +> const print = std.debug.print; +152c152 +< levelUp(glorp, reward_xp); +--- +> levelUp(&glorp, reward_xp); +157c157 +< fn levelUp(character_access: Character, xp: u32) void { +--- +> fn levelUp(character_access: *Character, xp: u32) void { |