diff --git a/build.zig b/build.zig index 5610997..fa70eb3 100644 --- a/build.zig +++ b/build.zig @@ -348,6 +348,11 @@ const exercises = [_]Exercise{ .main_file = "069_comptime4.zig", .output = "s1={ 1, 2, 3 }, s2={ 1, 2, 3, 4, 5 }, s3={ 1, 2, 3, 4, 5, 6, 7 }", }, + .{ + .main_file = "070_comptime5.zig", + .output = "\"Quack.\" ducky1: true, \"Squeek!\" ducky2: true, ducky3: false", + .hint = "Have you kept the wizard hat on?", + }, }; /// Check the zig version to make sure it can compile the examples properly. diff --git a/exercises/070_comptime5.zig b/exercises/070_comptime5.zig new file mode 100644 index 0000000..7934326 --- /dev/null +++ b/exercises/070_comptime5.zig @@ -0,0 +1,135 @@ +// +// Being able to pass types to functions at compile time lets us +// generate code that works with multiple types. But it doesn't +// help us pass VALUES of different types to a function. +// +// For that, we have the 'anytype' placeholder, which tells Zig +// to infer the actual type of a parameter at compile time. +// +// fn foo(thing: anytype) void { ... } +// +// Then we can use builtins such as @TypeOf(), @typeInfo(), +// @typeName(), @hasDecl(), and @hasField() to determine more +// about the type that has been passed in. All of this logic will +// be performed entirely at compile time. +// +const print = @import("std").debug.print; + +// Let's define three structs: Duck, RubberDuck, and Duct. Notice +// that Duck and RubberDuck both contain waddle() and quack() +// methods declared in their namespace (also known as "decls"). + +const Duck = struct { + eggs: u8, + loudness: u8, + location_x: i32 = 0, + location_y: i32 = 0, + + fn waddle(self: Duck, x: i16, y: i16) void { + self.location_x += x; + self.location_y += y; + } + + fn quack(self: Duck) void { + if (self.loudness < 4) { + print("\"Quack.\" ", .{}); + } else { + print("\"QUACK!\" ", .{}); + } + } +}; + +const RubberDuck = struct { + in_bath: bool = false, + location_x: i32 = 0, + location_y: i32 = 0, + + fn waddle(self: RubberDuck, x: i16, y: i16) void { + self.location_x += x; + self.location_y += y; + } + + fn quack(self: RubberDuck) void { + print("\"Squeek!\" ", .{}); + } +}; + +const Duct = struct { + diameter: u32, + length: u32, + galvanized: bool, + connection: ?*Duct = null, + + fn connect(self: Duct, other: *Duct) !void { + if (self.diameter == other.diameter) { + self.connection = other; + } else { + return DuctError.UnmatchedDiameters; + } + } +}; + +const DuctError = error{UnmatchedDiameters}; + +pub fn main() void { + // This is a real duck! + const ducky1 = Duck{ + .eggs = 0, + .loudness = 3, + }; + + // This is not a real duck, but it has quack() and waddle() + // abilities, so it's still a "duck". + const ducky2 = RubberDuck{ + .in_bath = false, + }; + + // This is not even remotely a duck. + const ducky3 = Duct{ + .diameter = 17, + .length = 165, + .galvanized = true, + }; + + print("ducky1: {}, ", .{isADuck(ducky1)}); + print("ducky2: {}, ", .{isADuck(ducky2)}); + print("ducky3: {}\n", .{isADuck(ducky3)}); +} + +// This function has a single parameter which is inferred at +// compile time. It uses builtins @TypeOf() and @hasDecl() to +// perform duck typing ("if it walks like a duck and it quacks +// like a duck, then it must be a duck") to determine if the type +// is a "duck". +fn isADuck(possible_duck: anytype) bool { + // We'll use @hasDecl() to determine if the type has + // everything needed to be a "duck". + // + // In this example, 'has_increment' will be true if type Foo + // has an increment() method: + // + // const has_increment = @hasDecl(Foo, "increment"); + // + // Please make sure MyType has both waddle() and quack() + // methods: + const MyType = @TypeOf(possible_duck); + const walks_like_duck = ???; + const quacks_like_duck = ???; + + const is_duck = walks_like_duck and quacks_like_duck; + + if (is_duck) { + // We also call the quack() method here to prove that Zig + // allows us to perform duck actions on anything + // sufficiently duck-like. + // + // Because all of the checking and inference is performed + // at compile time, we still have complete type safety: + // attempting to call the quack() method on a struct that + // doesn't have it (like Duct) would result in a compile + // error, not a runtime panic or crash! + possible_duck.quack(); + } + + return is_duck; +} diff --git a/patches/patches/070_comptime5.patch b/patches/patches/070_comptime5.patch new file mode 100644 index 0000000..1c56719 --- /dev/null +++ b/patches/patches/070_comptime5.patch @@ -0,0 +1,6 @@ +116,117c116,117 +< const walks_like_duck = ???; +< const quacks_like_duck = ???; +--- +> const walks_like_duck = @hasDecl(MyType, "waddle"); +> const quacks_like_duck = @hasDecl(MyType, "quack");