Skip to content

Instantly share code, notes, and snippets.

@eamonburns
Created November 7, 2025 21:01
Show Gist options
  • Select an option

  • Save eamonburns/901b0699f28d1c6474b9211f1a3b7225 to your computer and use it in GitHub Desktop.

Select an option

Save eamonburns/901b0699f28d1c6474b9211f1a3b7225 to your computer and use it in GitHub Desktop.
An attempt at creating a stack of variable scopes, but I can't figure out why it's leaking :/
//! Leaking hash map
//!
const std = @import("std");
const assert = std.debug.assert;
const print = std.debug.print;
const Allocator = std.mem.Allocator;
const Stack = @This();
pub const Value = union(enum) {
nil,
number: f32,
string: []const u8,
};
scopes: std.ArrayList(Scope),
pub const Scope = struct {
values: std.StringHashMapUnmanaged(Value),
pub const init = Scope{
.values = .empty,
};
pub fn deinit(self: *@This(), gpa: Allocator) void {
// Section A:
// - If I uncomment this section alone, nothing happens
// - If I uncomment this section while "Section B" is also edited (comment
// out "return" line, and deinitialize scope early), then I get an "Invalid free" error
// var it = self.values.iterator();
// while (it.next()) |entry| {
// gpa.destroy(entry.value_ptr);
// }
self.values.deinit(gpa);
}
};
pub fn init(gpa: Allocator) !Stack {
var stack: Stack = .{
.scopes = try .initCapacity(gpa, 1),
};
try stack.pushScope(gpa);
return stack;
}
pub fn deinit(self: *Stack, gpa: Allocator) void {
while (self.popScope(gpa)) |_| continue;
self.scopes.deinit(gpa);
}
pub fn declare(self: *Stack, gpa: Allocator, name: []const u8, value: Value) !void {
var scope = self.scopes.getLastOrNull() orelse return error.NoScopes;
if (self.get(name)) |_| return error.AlreadyDeclared;
// Section B:
// - If I comment out the "return" line, and uncomment the two
// following lines (put the new value, but deinit the scope early), the
// leak goes away, but that means I didn't really put the new value
return scope.values.put(gpa, name, value);
// try scope.values.put(gpa, name, value);
// scope.deinit(gpa);
}
pub fn get(self: *Stack, name: []const u8) ?Value {
assert(self.scopes.items.len > 0);
var i: usize = self.scopes.items.len - 1;
var value: ?Value = null;
while (i > 0 and value == null) : (i -= 1) {
value = self.scopes.items[i].values.get(name);
}
return value;
}
// pub fn set(self: *Stack, gpa: Allocator, name: []const u8, value: Value) !void {
// _ = self;
// _ = gpa;
// _ = name;
// _ = value;
// return error.Unimplemented;
// }
pub fn pushScope(self: *Stack, gpa: Allocator) !void {
return self.scopes.append(gpa, .init);
}
/// Remove and deinitialize the scope at the top of the stack.
/// Returns `null` if the stack is already empty.
pub fn popScope(self: *Stack, gpa: Allocator) ?void {
var scope = self.scopes.getLastOrNull() orelse return null;
const new_length = self.scopes.items.len - 1;
scope.deinit(gpa);
self.scopes.shrinkRetainingCapacity(new_length);
return;
}
test Stack {
const expectEqualDeep = std.testing.expectEqualDeep;
const gpa = std.testing.allocator;
var stack: Stack = try .init(gpa);
defer stack.deinit(gpa);
try expectEqualDeep(null, stack.get("a"));
try expectEqualDeep(null, stack.get("b"));
try expectEqualDeep(null, stack.get("c"));
try stack.declare(gpa, "a", .nil);
// try stack.declare(gpa, "b", .{ .number = 3.14 });
// try stack.declare(gpa, "c", .{ .string = "hi" });
//
// try expectEqualDeep(Value.nil, stack.get("a"));
// try expectEqualDeep(Value{ .number = 3.14 }, stack.get("b"));
// try expectEqualDeep(Value{ .string = "hi" }, stack.get("c"));
//
// {
// try stack.pushScope(gpa);
//
// try expectEqualDeep(Value.nil, stack.get("a"));
// try expectEqualDeep(Value{ .number = 3.14 }, stack.get("b"));
// try expectEqualDeep(Value{ .string = "hi" }, stack.get("c"));
//
// try expectEqualDeep(null, stack.get("x"));
// try stack.declare(gpa, "x", .nil);
// try expectEqualDeep(Value.nil, stack.get("x"));
//
// try stack.set(gpa, "a", .{ .string = "thing" });
// try expectEqualDeep(Value{ .string = "thing" }, stack.get("a"));
//
// stack.popScope(gpa).?;
// }
//
// try expectEqualDeep(null, stack.get("x"));
//
// try expectEqualDeep(Value{ .string = "thing" }, stack.get("a"));
// try expectEqualDeep(Value{ .number = 3.14 }, stack.get("b"));
// try expectEqualDeep(Value{ .string = "hi" }, stack.get("c"));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment