Best way to extend std

Very new to zig I’m from the ruby, python, js world. I’m learning zig for something different and extend my knowledge behind higher level languages. So one thing I like about ruby for example is it’s easy to add to and extend the base functionality.
I’m playing around with parsing a txt file. and have some code like

var it = std.mem.splitScalar(u8, line, ' ');
var list = try toArrayList(alloc, []const u8, it);
defer list.deinit(alloc);

pub fn toArrayList(alloc: Allocator, comptime T: type, it_in: anytype) !std.ArrayList(T) {
    var it = it_in;
    var list = try std.ArrayList(T).initCapacity(alloc, 8);
    errdefer list.deinit(alloc);
    while (it.next()) |part| {
        try list.append(alloc, part);
    }
    return list;
}

what I would like the code to look like is something like this.
var list = try it.toArrayList(alloc, []const u8);

Vibing would give examples of creating your own Struct of splitScalerExt and calling that or using namespace which is no longer support.

1 Like

Zig does not allow extending types or expanding them with your own functionality.

So your options are quite limited:

  1. external helper function (what you have right there)
  2. wrapper struct, where every function of the base type needs to be manually passed through
const SplitIteratorWrapper = struct { // Hardcoded for u8, could easily make this generic if needed
    child: ... // type of the thing

    pub fn init(child: ...) SplitIteratorWrapper {
        return .{
            .child = child,
        };
    }
    
    pub fn next(self: *SplitIteratorWrapper) ?[]const u8 {
        return self.child.next();
    }

    pub fn toArrayList(...) {...} // your code goes here
};
  1. make it your own, take the functionality and copy it into your own project. This requires more maintenance, but gives you the ultimate freedom.
5 Likes

A nice thing about Zig is that option 1 is always a possibility since containers can’t have private fields, so you can access the entire local state of any container even if you’re not inside of its namespace.

1 Like

Thank you for the feedback. Bummer there isn’t an option to do. std.mem.splitScalar extend { pub fn addedFunction(*Self){}} so compile time monkey patching.

If this kind of monkey patching adds declarations directly based on the original type name, in Zig’s philosophy, it is usually considered an anti-pattern, because doing so makes it difficult for code readers to find where a particular declaration of a struct actually resides.

However, I would be happy to see this kind of extension pattern:

const MySplitIterator = std.mem.SplitIterator extend { 
    pub fn addedFunction(self *@This()) void {} 
};

That is, allowing the original type’s declaration to be extended under a different type name, ensuring that the extended type has the same memory layout as the original type, and that values of the extended type and original type can be implicitly converted. To prevent readability from being reduced by multiple layers of extension, extensions are limited to only one layer.

I’m not sure if even this level of extension violates Zig’s philosophy, since this effect is somewhat similar to the already removed using namespace. But I believe this usage is basically like allowing the introduction of only one type’s namespace, which is friendly enough for readers.

4 Likes