Skip to content

Instantly share code, notes, and snippets.

@denisdefreyne
Last active November 6, 2025 10:05
Show Gist options
  • Select an option

  • Save denisdefreyne/0ca71bc875713cb69132660323afb9fd to your computer and use it in GitHub Desktop.

Select an option

Save denisdefreyne/0ca71bc875713cb69132660323afb9fd to your computer and use it in GitHub Desktop.
A reference-counting implementation using an interface
const std = @import("std");
const Rc = @This();
ptr: *anyopaque,
ref_count_ptr: *u8,
deinit: *const fn (self: *const anyopaque) void,
pub fn refCount(self: *const Rc) u8 {
return self.ref_count_ptr.*;
}
pub fn retain(self: *const Rc) void {
// Increment ref count
self.ref_count_ptr.* = self.ref_count_ptr.* + 1;
}
pub fn release(self: *const Rc) void {
// Decrement ref count
const new_ref_count = self.ref_count_ptr.* - 1;
self.ref_count_ptr.* = new_ref_count;
// Dealloc, if needed
if (new_ref_count == 0) {
self.deinit(self.ptr);
}
}
pub fn format(
self: Rc,
writer: anytype,
) !void {
try writer.print("Rc({})", .{self.ref_count_ptr.*});
}
const std = @import("std");
const Rc = @import("./rc.zig");
const Self = @This();
// Rc props
ref_count: u8 = 1,
// Generic props
content: union(enum) {
owned: struct {
data: []const u8,
allocator: std.mem.Allocator,
},
owned_z: struct {
data: [:0]const u8,
allocator: std.mem.Allocator,
},
constant_slice: []const u8,
reference_slice: struct {
data: []const u8,
rc: Rc,
},
},
// Rc interface
fn rc_deinit(ptr: *const anyopaque) void {
const self: *const Self = @ptrCast(@alignCast(ptr));
self.deinit();
}
pub fn rc(self: *Self) Rc {
return .{
.ptr = @ptrCast(self),
.ref_count_ptr = &self.ref_count,
.deinit = rc_deinit,
};
}
// Generic interface
pub fn deinit(self: *const Self) void {
switch (self.content) {
.owned => |s| {
s.allocator.free(s.data);
},
.owned_z => |s| {
s.allocator.free(s.data);
},
.constant_slice => {
// do nothing
},
.reference_slice => |s| {
s.rc.release();
},
}
}
pub fn getSlice(self: Self) []const u8 {
return switch (self.content) {
.owned => |s| s.data,
.owned_z => |s| s.data,
.constant_slice => |s| s,
.reference_slice => |s| s.data,
};
}
pub fn fromOwned(allocator: std.mem.Allocator, data: []const u8) Self {
return .{
.content = .{
.owned = .{
.allocator = allocator,
.data = data,
},
},
};
}
test "alloc + init + retain + release - owned" {
var string = Self.fromOwned(
std.testing.allocator,
try std.testing.allocator.dupe(u8, "hi"),
);
try std.testing.expectEqualStrings("hi", string.getSlice());
try std.testing.expectEqual(1, string.rc().refCount());
string.rc().retain();
try std.testing.expectEqual(2, string.rc().refCount());
string.rc().release();
try std.testing.expectEqual(1, string.rc().refCount());
string.rc().release();
try std.testing.expectEqual(0, string.rc().refCount()); // OK because still on stack
}
pub fn fromOwnedZ(allocator: std.mem.Allocator, data: [:0]const u8) Self {
return .{
.content = .{
.owned_z = .{
.allocator = allocator,
.data = data,
},
},
};
}
test "alloc + init + retain + release - owned_z" {
var string = Self.fromOwnedZ(
std.testing.allocator,
try std.testing.allocator.dupeZ(u8, "hi"),
);
try std.testing.expectEqualStrings("hi", string.getSlice());
try std.testing.expectEqual(1, string.rc().refCount());
string.rc().retain();
try std.testing.expectEqual(2, string.rc().refCount());
string.rc().release();
try std.testing.expectEqual(1, string.rc().refCount());
string.rc().release();
try std.testing.expectEqual(0, string.rc().refCount()); // OK because still on stack
}
pub fn fromConstant(data: []const u8) Self {
return .{
.content = .{
.constant_slice = data,
},
};
}
test "alloc + init + retain + release - constant" {
var string = Self.fromConstant(
"hi",
);
try std.testing.expectEqualStrings("hi", string.getSlice());
try std.testing.expectEqual(1, string.rc().refCount());
string.rc().retain();
try std.testing.expectEqual(2, string.rc().refCount());
string.rc().release();
try std.testing.expectEqual(1, string.rc().refCount());
string.rc().release();
try std.testing.expectEqual(0, string.rc().refCount()); // OK because still on stack
}
pub fn fromReferenced(data: []const u8, referenced_rc: Rc) Self {
referenced_rc.retain();
return .{
.content = .{
.reference_slice = .{
.data = data,
.rc = referenced_rc,
},
},
};
}
test "alloc + init + retain + release - referenced" {
var orig_string = Self.fromOwnedZ(
std.testing.allocator,
try std.testing.allocator.dupeZ(u8, "hi"),
);
var string = Self.fromReferenced(
"hi",
orig_string.rc(),
);
orig_string.rc().release();
try std.testing.expectEqualStrings("hi", string.getSlice());
try std.testing.expectEqual(1, string.rc().refCount());
string.rc().retain();
try std.testing.expectEqual(2, string.rc().refCount());
string.rc().release();
try std.testing.expectEqual(1, string.rc().refCount());
string.rc().release();
try std.testing.expectEqual(0, string.rc().refCount()); // OK because still on stack
try std.testing.expectEqual(0, orig_string.rc().refCount()); // OK because still on stack
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment