Easy and Straightforward OOP in zig

Just found about this library, that cleverly use comptime capabilities and pub usingnamespace to allow interfaces and inheritance as if it was part of the language.

The author have made a video, check it out:

Code from the README

zig

// examples/animals.zig
const std = @import("std");

const interface = @import("interface");

fn AnimalInterface(comptime SelfType: type) type {
    return struct {
        pub const Self = SelfType;

        pub fn speak(self: *const Self) void {
            return interface.VirtualCall(self, "speak", .{}, void);
        }

        pub fn describe(self: *const Self) void {
            return interface.VirtualCall(self, "describe", .{}, void);
        }

        pub fn play(self: *const Self, toy: []const u8) void {
            return interface.VirtualCall(self, "play", .{toy}, void);
        }

        pub fn delete(self: *Self, allocator: std.mem.Allocator) void {
            return interface.VirtualCall(self, "delete", .{allocator}, void);
        }
    };
}

const IAnimal = interface.ConstructInterface(AnimalInterface);

const Animal = struct {
    pub usingnamespace interface.DeriveFromBase(IAnimal, Animal);

    name: []const u8,
    age: u32,

    pub fn describe(self: *const Animal) void {
        std.debug.print("{s} is {d} years old.\n", .{ self.name, self.age });
    }
};

const Dog = struct {
    pub usingnamespace interface.DeriveFromBase(Animal, Dog);
    base: Animal,
    breed: []const u8,

    pub fn create(name: []const u8, age: u32, breed: []const u8) Dog {
        return Dog{ .base = Animal{
            .name = name,
            .age = age,
        }, .breed = breed };
    }

    pub fn speak(self: *const Dog) void {
        std.debug.print("{s} says: Woof!\n", .{self.base.name});
    }

    pub fn describe(self: *const Dog) void {
        std.debug.print("{s} is {d} years old {s}.\n", .{ self.base.name, self.base.age, self.breed });
    }

    pub fn play(self: *const Dog, toy: []const u8) void {
        std.debug.print("{s} doesn't like: {s}.\n", .{ self.base.name, toy });
    }
};

const Cat = struct {
    pub usingnamespace interface.DeriveFromBase(Animal, Cat);
    base: Animal,

    pub fn create(name: []const u8, age: u32) Cat {
        return Cat{ .base = Animal{
            .name = name,
            .age = age,
        } };
    }

    pub fn speak(self: *const Cat) void {
        std.debug.print("{s} says: Meow!\n", .{self.base.name});
    }

    pub fn play(self: *const Cat, toy: []const u8) void {
        std.debug.print("{s} plays with {s}.\n", .{ self.base.name, toy });
    }
};

pub fn test_animal(animal: IAnimal) void {
    animal.describe();
    animal.speak();
    animal.play("Mouse");
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{
        .safety = true,
    }){};
    const allocator = gpa.allocator();
    defer _ = gpa.deinit();

    var cat = try Cat.create("Garfield", 7).new(allocator);
    defer cat.delete(allocator);
    var dog = try Dog.create("Lassie", 12, "Rough Collie").new(allocator);
    defer dog.delete(allocator);
    test_animal(cat);
    std.debug.print("\n", .{});
    test_animal(dog);
}```

</details>
1 Like

usingnamespace will be removed in 0.15

frankly, you should not attempt to use OOP in Zig unless you have no other choice.

we’re working on bindings for Godot, which puts us in such a position. Godot is pinnacle OOP. I spun out a little utility library that lets you work with Godot’s flavor of it, which may or may not be useful for other use-cases. It is named oopz, because again, using OOP in Zig is almost always a mistake:

11 Likes