Adding a few more ways of doing interfaces in zig

This commit is contained in:
2026-05-04 19:41:43 -05:00
parent d3dfa12408
commit d029d3c3cd
3 changed files with 109 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# Test runs all the tests
test:
zig test src/root.zig
+35
View File
@@ -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));
}
+71
View File
@@ -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()));
}