Adding a few more ways of doing interfaces in zig
This commit is contained in:
@@ -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));
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
Reference in New Issue
Block a user