Last active
November 6, 2025 10:05
-
-
Save denisdefreyne/0ca71bc875713cb69132660323afb9fd to your computer and use it in GitHub Desktop.
A reference-counting implementation using an interface
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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.*}); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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