Currently the managed variations of ArrayList are marked as deprecated and I think that’s the right choice, but providing a way to opt-in to higher level ways of managing memory I think is valuable to have.
The dream would be:
// AFAIK this can't be done
test "struct managed memory" {
// we opt-in to the choice of having the struct keep track of it's allocator
var arr: Managed(std.array_list.Aligned(u8, null)) = .initCapacity(std.testing.allocator, 10);
defer arr.deinit();
try arr.append(10);
}
I’ve been able to create a working proof of concept that gets as far as the following:
// sc == Static call
// ic == Instance call
// see full implementation below for details
test "struct managed memory" {
var arr: Managed(std.array_list.Aligned(u8, null)) = .sc("initCapacity", .{std.testing.allocator, 10});
defer arr.ic("deinit", .{});
try arr.ic("append", .{10});
try arr.ic("append", .{20});
try arr.ic("append", .{30});
}
Full implementation
// zig 0.15.2
const std = @import("std");
pub fn Managed(comptime T: type) type {
const ti = @typeInfo(T);
if (ti != .@"struct") @compileError("Can only manage structs.");
const M = struct {
allocator: std.mem.Allocator,
managed: T,
/// `init` methods will need to return the type `T`, wrapped in the managed struct `M`
fn willMakeManagedInstance(comptime name: []const u8) type {
if (errorlessType(fnReturnType(T, name)) == T) return @This();
return void;
}
/// Static call
pub fn sc(comptime name: []const u8, args: anytype) willMakeManagedInstance(name) {
const method = @field(T, name);
const method_ti = fnTypeInfo(T, name);
if (method_ti.params.len > 0 and method_ti.params[0].type == T) @compileError("Use ic instead.");
if (errorlessType(fnReturnType(T, name)) == T) {
return @This(){
// WARN: just assumes the first arg is the allocator, should actually check
.allocator = args[0],
// WARN: just a hacky fix to not address errors for now
.managed = @call(.auto, method, args) catch @panic("oom"),
};
}
}
// Instance call
pub fn ic(self: *@This(), comptime name: []const u8, args: anytype) fnReturnType(T, name) {
const method = @field(T, name);
// TODO: should probably check if the param of the method being called actually needs an allocator
return @call(.auto, method, .{&self.managed, self.allocator} ++ args);
}
};
return M;
}
// Example
// test "struct managed memory" {
// var arr: Managed(std.array_list.Aligned(u8, null)) = .initCapacity(std.testing.allocator, 10);
// defer arr.deinit();
//
// try arr.append(10);
// }
test "struct managed memory" {
const ManagedArrayList = Managed(std.array_list.Aligned(u8, null));
var arr: ManagedArrayList = .sc("initCapacity", .{std.testing.allocator, 10});
defer arr.ic("deinit", .{});
// ideally it would be nice to create an actual function in some way so that you could just
// do `try arr.append(10)`.
try arr.ic("append", .{10});
try arr.ic("append", .{20});
try arr.ic("append", .{30});
}
// Type Helper functions
/// Get function type info of named function.
fn fnTypeInfo(comptime T: type, comptime name: []const u8) std.builtin.Type.Fn {
const field = @field(T, name);
const field_ti = @typeInfo(@TypeOf(field));
if (field_ti != .@"fn") @compileError("Named field is not a function.");
return field_ti.@"fn";
}
/// Get return type of named function.
fn fnReturnType(comptime T: type, comptime name: []const u8) type {
const method = fnTypeInfo(T, name);
const method_rt = method.return_type.?;
return method_rt;
}
fn errorlessType(comptime T: type) type {
const ti = @typeInfo(T);
if (ti == .error_union) {
// We don't care about the error part in this example.
return ti.error_union.payload;
}
return T;
}
This was just an afternoon experiment so am sure could improve it a bit further but the current comptime limitations I think would prevent my ideal case. I’m using array lists as the example but the extended goal is that this is generic to anything that does allocations in it’s functions.
For some context, I’m coming from a background where I’ve done a lot of tutoring and teaching programming across a lot of languages and skill levels, so accessibility and ease of use is something I’m always thinking about even when working on my own projects.
I like zig a lot and would be cool if I could have it in my cards of choice when someone asks me about learning to make something.