My favorite way of doing initialization is to have 2 “constructors”, one for stack allocation and the other one for heap allocation. Using your linked list example it would be something like this:
const std = @import("std");
const Allocator = std.mem.Allocator;
const Node = packed struct {
value: i32,
next: ?*Node = null,
fn init(val: i32) Node {
return .{.value = val};
}
fn create(val: i32, a: Allocator) !*Node {
const np = try a.create(Node);
np.* = init(val);;
return np;
}
};
fn printNodes(n: *const Node) void {
var maybe_node: ?*const Node = n;
while (maybe_node) |node| {
std.debug.print("node.value {d}\n", .{node.value});
std.debug.print("node 0x{x:0>24}\n", .{@as(u96, @bitCast(node.*))});
maybe_node = node.next;
}
}
pub fn main() !void {
const stackNode = Node.init(1);
printNodes(&stackNode);
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var allocator = gpa.allocator();
const heapNode = try Node.create(2, allocator);
defer allocator.destroy(heapNode);
printNodes(heapNode);
}