|
|
|
@@ -0,0 +1,101 @@
|
|
|
|
|
const std = @import("std");
|
|
|
|
|
/// Here `Animal` is our interface that we want to implement. It itself is a struct
|
|
|
|
|
/// whose state will map the functions calls back to the parent struct which holds
|
|
|
|
|
/// it.
|
|
|
|
|
pub const Animal = struct {
|
|
|
|
|
// ageFn is the function which `age` calls. The function uses a `const` Animal
|
|
|
|
|
// which makes sure we don't change the underlying Animal.
|
|
|
|
|
ageFn: *const fn (self: *const Animal) usize,
|
|
|
|
|
|
|
|
|
|
// setAgeFn is the function we can set which is called by `setAge` in the
|
|
|
|
|
// interface.
|
|
|
|
|
setAgeFn: *const fn (self: *Animal, age: usize) void,
|
|
|
|
|
|
|
|
|
|
// Here we have an optional function with a default implementation when
|
|
|
|
|
// not specified. Check out `weight`
|
|
|
|
|
weightFn: ?*const fn (self: *const Animal) f64,
|
|
|
|
|
|
|
|
|
|
// These functions "drive" the interface by calling the setable functions
|
|
|
|
|
// above. The only exciting thing here is weight, where the default
|
|
|
|
|
// implementation is used.
|
|
|
|
|
pub fn age(self: *const Animal) usize {
|
|
|
|
|
return self.ageFn(self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn setAge(self: *Animal, newAge: usize) void {
|
|
|
|
|
return self.setAgeFn(self, newAge);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn weight(self: *const Animal) f64 {
|
|
|
|
|
// here we check if the function for weight has been implemented. If it
|
|
|
|
|
// hasn't re utilize the underlying default implementation. This is
|
|
|
|
|
// done at runtime
|
|
|
|
|
// TODO: how would you do this at comptime
|
|
|
|
|
if (self.weightFn) |weightFn| return weightFn(self) else return self.defaultWeight();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn defaultWeight(_: *const Animal) f64 {
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Now we create an implemetor of the interface, a Dog, which is an animal.
|
|
|
|
|
pub const Dog = struct {
|
|
|
|
|
dogAge: usize,
|
|
|
|
|
// the interface exists as a field on the implemtenting struct. When we have
|
|
|
|
|
// a pointer to the field `interface` we can calculate the pointer to the parent
|
|
|
|
|
// `Dog`. Since zig uses LLVM there is no knowing how the compiler actually stores
|
|
|
|
|
// the struct in memory. We will see how to get around that soon.
|
|
|
|
|
interface: Animal,
|
|
|
|
|
|
|
|
|
|
pub fn init() Dog {
|
|
|
|
|
return .{
|
|
|
|
|
.dogAge = 10,
|
|
|
|
|
// When creating the interface we have to manually map each function
|
|
|
|
|
// to the interface. Here we reference the adress of each of these
|
|
|
|
|
// fuctions, but we don't carry the context of _what_ parent to call
|
|
|
|
|
// it for. That is why we have to explicity call the first argument
|
|
|
|
|
// in the `age` function:
|
|
|
|
|
//
|
|
|
|
|
// ```zig
|
|
|
|
|
// return self.ageFn(self);
|
|
|
|
|
// ^ explicit reference
|
|
|
|
|
// ```
|
|
|
|
|
.interface = .{
|
|
|
|
|
.ageFn = Dog.age,
|
|
|
|
|
.setAgeFn = Dog.setAge,
|
|
|
|
|
.weightFn = null,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now we are in the function, `a` is the animal pointer to the field we care
|
|
|
|
|
// about, and we can call @fieldParentPtr to get pointer of the parent Dog.
|
|
|
|
|
// from there we can finish implementing the interface.
|
|
|
|
|
// Notice the `Animal` is referenced as the parent since the calling context
|
|
|
|
|
// is relative to that field, not the parent object.
|
|
|
|
|
fn age(a: *const Animal) usize {
|
|
|
|
|
const prt: *const Dog = @fieldParentPtr("interface", a);
|
|
|
|
|
return prt.dogAge;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We can also use a `var Animal` so we can manipulate the parent object as well
|
|
|
|
|
fn setAge(a: *Animal, newAge: usize) void {
|
|
|
|
|
var prt: *Dog = @fieldParentPtr("interface", a);
|
|
|
|
|
prt.dogAge = newAge;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
test "intrusive interface" {
|
|
|
|
|
const testing = @import("std").testing;
|
|
|
|
|
var dog = Dog.init();
|
|
|
|
|
// we can call it now! you can see we call `dog.interface.age` since the function
|
|
|
|
|
// first argument is the interface itself, and not the underlying struct
|
|
|
|
|
try testing.expectEqual(10, dog.interface.age());
|
|
|
|
|
// This would also work, though is a little odd, but it might highlight what is
|
|
|
|
|
// going on under the covers.
|
|
|
|
|
try testing.expectEqual(10, Dog.age(&dog.interface));
|
|
|
|
|
dog.interface.setAge(15);
|
|
|
|
|
try testing.expectEqual(15, dog.interface.age());
|
|
|
|
|
try testing.expectEqual(0.0, dog.interface.weight());
|
|
|
|
|
}
|