diff --git a/README.md b/README.md index cd4f397..e5e8ac9 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ Planned exercises: * [x] Struct methods * [x] Slices * [x] Many pointers -* [ ] Unions +* [x] Unions * [ ] Numeric types (integers, floats) * [ ] Labelled blocks and loops * [ ] Loops as expressions diff --git a/build.zig b/build.zig index 75fa9a9..35f00a1 100644 --- a/build.zig +++ b/build.zig @@ -280,6 +280,10 @@ const exercises = [_]Exercise{ .main_file = "54_manypointers.zig", .output = "Memory is a resource.", }, + .{ + .main_file = "55_unions.zig", + .output = "Insect report! Ant alive is: true. Bee visited 15 flowers.", + }, }; /// Check the zig version to make sure it can compile the examples properly. diff --git a/exercises/55_unions.zig b/exercises/55_unions.zig new file mode 100644 index 0000000..5e08aa1 --- /dev/null +++ b/exercises/55_unions.zig @@ -0,0 +1,76 @@ +// +// 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 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: + var ant = Insect{ .still_alive = true }; + var bee = Insect{ .flowers_visited = 15 }; + + std.debug.print("Insect report! ", .{}); + + // Oops! We've made a mistake here. + printInsect(ant, AntOrBee.c); + printInsect(bee, AntOrBee.c); + + 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}), + } +} diff --git a/patches/patches/55_unions.patch b/patches/patches/55_unions.patch new file mode 100644 index 0000000..c362f20 --- /dev/null +++ b/patches/patches/55_unions.patch @@ -0,0 +1,6 @@ +62,63c62,63 +< printInsect(ant, AntOrBee.c); +< printInsect(bee, AntOrBee.c); +--- +> printInsect(ant, AntOrBee.a); +> printInsect(bee, AntOrBee.b);