From d029d3c3cdc19c2a0f2e8007d941f3f578547d0f Mon Sep 17 00:00:00 2001 From: Paul Montag Date: Mon, 4 May 2026 19:41:43 -0500 Subject: [PATCH] Adding a few more ways of doing interfaces in zig --- justfile | 3 ++ src/anytype_polymorphism.zig | 35 ++++++++++++++++++ src/interface.zig | 71 ++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 justfile create mode 100644 src/anytype_polymorphism.zig create mode 100644 src/interface.zig diff --git a/justfile b/justfile new file mode 100644 index 0000000..23385aa --- /dev/null +++ b/justfile @@ -0,0 +1,3 @@ +# Test runs all the tests +test: + zig test src/root.zig diff --git a/src/anytype_polymorphism.zig b/src/anytype_polymorphism.zig new file mode 100644 index 0000000..54f833f --- /dev/null +++ b/src/anytype_polymorphism.zig @@ -0,0 +1,35 @@ +const std = @import("std"); + +// Create a type, Dog, and give it a "speak" function with the right +// function +const Dog = struct { + pub fn speak(_: *const @This()) []const u8 { + return @as([]const u8, "woof"); + } +}; + +// Then do the same thing for a Cat. It has the same function +const Cat = struct { + pub fn speak(_: *const @This()) []const u8 { + return @as([]const u8, "meow"); + } +}; + +// Now using `anytype` we can assume the type passed in has the +// methods required. +fn speak(t: anytype) []const u8 { + return t.speak(); +} + +// We can now use it, and it compiles because everything is happy at compiletime +// however, using this interface is confusing, what are we supposed to implement +// to use it safely?! +test "Can Speak" { + const cat: Cat = .{}; + const dog: Dog = .{}; + + try std.testing.expectEqual("meow", speak(&cat)); + try std.testing.expectEqual("meow", speak(cat)); + try std.testing.expectEqual("woof", speak(&dog)); + try std.testing.expectEqual("woof", speak(dog)); +} diff --git a/src/interface.zig b/src/interface.zig new file mode 100644 index 0000000..955ce99 --- /dev/null +++ b/src/interface.zig @@ -0,0 +1,71 @@ +// Animal is our "interface" it houses a pointer to the animal, also known +// as the context, and any functions that we want to call on it +const Animal = struct { + // animal is the context, this is a pointer to the struct which is + // implementing the interface + animal: *anyopaque, + + // speakFn is a pointer to the function which implements the interface + speakFn: *const fn (self: *anyopaque) []const u8, + + // Now we actually drive calling the function, here you can see we pass + // the anyopaque pointer to the speak function directly + pub fn speak(self: *const @This()) []const u8 { + return self.speakFn(self.animal); + } +}; + +// Dog is a struct which implements the Animal struct +const Dog = struct { + // here `speak` is called with the pointer, below you can see an example + // of how we turn that back into a pointer of the concrete type. + pub fn speak(ptr: *anyopaque) []const u8 { + // example of how to get self from this + const self: *@This() = @ptrCast(ptr); + // fake usage to shut the compiler up + _ = self; + + return @as([]const u8, "woof"); + } + + // in order to get an instance of the interface we call this helper + // function which creates the interface. We fill the `.animal` field + // with a pointer to the implementing struct, and pass in the + // implementing functions. + pub fn animal(self: *@This()) Animal { + return .{ + .animal = self, + .speakFn = @This().speak, + }; + } +}; + +// Lets do it again with the `Cat` type just to show how good we are at +// creating structs +const Cat = struct { + pub fn speak(_: *anyopaque) []const u8 { + return @as([]const u8, "meow"); + } + + pub fn animal(self: *@This()) Animal { + return .{ + .animal = self, + .speakFn = @This().speak, + }; + } +}; + +// This is an overly simplistic use of our Animal interface, we just call +// the speak function. +pub fn speak(a: Animal) []const u8 { + return a.speak(); +} + +test "Interface" { + const testing = @import("std").testing; + var dog: Dog = .{}; + var cat: Cat = .{}; + + try testing.expectEqual("woof", speak(dog.animal())); + try testing.expectEqual("meow", speak(cat.animal())); +}