From ab8ef155123d15869ed4a64eac9bb685974a9b7b Mon Sep 17 00:00:00 2001 From: Paul Montag Date: Sun, 5 Apr 2026 18:21:48 -0500 Subject: [PATCH] Initial commit --- .gitignore | 1 + build.zig | 5 ++ build.zig.zon | 7 +++ src/intrusive_interface.zig | 101 ++++++++++++++++++++++++++++++++++++ src/root.zig | 4 ++ 5 files changed, 118 insertions(+) create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 src/intrusive_interface.zig create mode 100644 src/root.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19892e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.zig-cache diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..e24f792 --- /dev/null +++ b/build.zig @@ -0,0 +1,5 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + _ = b; // stub +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..dd2edc7 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,7 @@ +.{ + .name = .zig_references, + .version = "0.0.1", + .minimum_zig_version = "0.16.0-dev.3041+3dc5f1398", + .paths = .{""}, + .fingerprint = 0xab9d2920d99601df, +} diff --git a/src/intrusive_interface.zig b/src/intrusive_interface.zig new file mode 100644 index 0000000..036fb12 --- /dev/null +++ b/src/intrusive_interface.zig @@ -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()); +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..f8ef2de --- /dev/null +++ b/src/root.zig @@ -0,0 +1,4 @@ +test "Everything" { + _ = @import("intrusive_interface.zig"); + @import("std").testing.refAllDecls(@This()); +}