Here is a simple tree-like structure representing mathematical expressions. I made two helper functions to create expression easily with this syn
pub const Binop = enum { ADD, SUB, DIV, MUL, POW };
pub const Expr = union(enum) {
integer: i128,
binop: struct {
op: Binop,
lhs: *Expr,
rhs: *Expr,
},
pub fn fromInteger(value: i128, allocator: std.mem.Allocator) std.mem.Allocator.Error!*Expr {
const e = try allocator.create(Expr);
e.* = Expr{ .integer = value };
return e;
}
pub fn fromBinop(op: Binop, lhs: *Expr, rhs: *Expr, allocator: std.mem.Allocator) std.mem.Allocator.Error!*Expr {
errdefer {
lhs.deinit(allocator);
rhs.deinit(allocator);
}
const e = try allocator.create(Expr);
e.* = Expr{ .binop = .{ .op = op, .lhs = lhs, .rhs = rhs } };
return e;
}
pub fn deinit(self: *Expr, allocator: std.mem.Allocator) void {
defer allocator.destroy(self);
switch (self.*) {
.binop => |binop| {
binop.lhs.deinit(allocator);
binop.rhs.deinit(allocator);
},
.integer => {
// Simple values don't have allocated children
},
}
}
};
Let’s say I want to create the operation 1 + 5, i will write
const expr = try Expr.fromBinop(.ADD,
try Expr.fromInteger(1, allocator),
try Expr.fromInteger(5, allocator),
allocator);
defer expr.deinit(allocator);
If the allocation in Expr.fromBinop fails the subexpressions will be deallocated thanks to the errdefer block and all is well.
But if the allocation in the second Expr.fromInteger fails the first Expr.fromInteger will leak memory
.
So let’s get out these allocations from the arguments and handle possible failure:
const a = try Expr.fromInteger(1, allocator);
errdefer a.deinit(allocator);
const b = try Expr.fromInteger(5, allocator);
errdefer b.deinit(allocator);
const expr = try Expr.fromBinop(.ADD, a, b, allocator);
defer expr.deinit(allocator);
Now if b allocation fails a will be correctly freed.
But if expr allocation fails, a and b will be double-freed.
The problem here is:
- The caller has to free
aandbuntil its passed toExpr.fromBinopwhich takes ownership of the variables. - After giving ownership
exprbecomes responsible for freeingaandb.
The possible solutions:
- Don’t care: if allocation fails children will leaks and its okayish
- Allocate in a block which returns
aandbdestructure and pass them toExpr.fromBinopdocumenting that this function takes ownership (error prone and quite verbose for a common situation). - Don’t take ownership and copy the arguments (possibly very costly for large expression).
What would be the idiomatic way in zig ?