123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- //
- // If you thought the last exercise was a deep dive, hold onto your
- // hat because we are about to descend into the computer's molten
- // core.
- //
- // (Shouting) DOWN HERE, THE BITS AND BYTES FLOW FROM RAM TO THE CPU
- // LIKE A HOT, DENSE FLUID. THE FORCES ARE INCREDIBLE. BUT HOW DOES
- // ALL OF THIS RELATE TO THE DATA IN OUR ZIG PROGRAMS? LET'S HEAD
- // BACK UP TO THE TEXT EDITOR AND FIND OUT.
- //
- // Ah, that's better. Now we can look at some familiar Zig code.
- //
- // @import() adds the imported code to your own. In this case, code
- // from the standard library is added to your program and compiled
- // with it. All of this will be loaded into RAM when it runs. Oh, and
- // that thing we name "const std"? That's a struct!
- //
- const std = @import("std");
- // Remember our old RPG Character struct? A struct is really just a
- // very convenient way to deal with memory. These fields (gold,
- // health, experience) are all values of a particular size. Add them
- // together and you have the size of the struct as a whole.
- const Character = struct{
- gold: u32 = 0,
- health: u8 = 100,
- experience: u32 = 0,
- };
- // Here we create a character called "the_narrator" that is a constant
- // (immutable) instance of a Character struct. It is stored in your
- // program as data, and like the instruction code, it is loaded into
- // RAM when your program runs. The relative location of this data in
- // memory is hard-coded and neither the address nor the value changes.
- const the_narrator = Character{
- .gold = 12,
- .health = 99,
- .experience = 9000,
- };
- // This "global_wizard" character is very similar. The address for
- // this data won't change, but the data itself can since this is a var
- // and not a const.
- var global_wizard = Character{};
- // A function is instruction code at a particular address. Function
- // parameters in Zig are always immutable. They are stored in "the
- // stack". A stack is a type of data structure and "the stack" is a
- // specific bit of RAM reserved for your program. The CPU has special
- // support for adding and removing things from "the stack", so it is
- // an extremely efficient place for memory storage.
- //
- // Also, when a function executes, the input arguments are often
- // loaded into the beating heart of the CPU itself in registers.
- //
- // Our main() function here has no input parameters, but it will have
- // a stack entry (called a "frame").
- pub fn main() void{
-
- // Here, the "glorp" character will be allocated on the stack
- // because each instance of glorp is mutable and therefore unique
- // to the invocation of this function.
-
- var glorp = Character{
- .gold = 30,
- };
-
- // The "reward_xp" value is interesting. It's an immutable
- // value, so even though it is local, it can be put in global
- // data and shared between all invocations. But being such a
- // small value, it may also simply be inlined as a literal
- // value in your instruction code where it is used. It's up
- // to the compiler.
-
- const reward_xp: u32 = 200;
-
- // Now let's circle back around to that "std" struct we imported
- // at the top. Since it's just a regular Zig value once it's
- // imported, we can also assign new names for its fields and
- // declarations. "debug" refers to another struct and "print" is a
- // public function namespaced within THAT struct.
- //
- // Let's assign the std.debug.print function to a const named
- // "print" so that we can use this new name later!
-
- const print = 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!
|