Files
ghostty/src/apprt/gtk/weak_ref.zig
Håkon Hægland 1dbc8ca30c apprt/gtk: add WeakRef.deinit and use it at teardown sites
A GWeakRef must be released before the memory holding it is freed: the
target keeps a pointer to the GWeakRef so it can clear it at finalize,
and if that memory is gone by then the target walks into whatever now
occupies it. inspector_window.zig already carries this warning, and
every call site follows it — but the rule lives in a comment in one
file, while the type itself offers only set and get, so releasing one
looks like an ordinary assignment.

Give it a name. deinit forwards to g_weak_ref_clear, which is the call
GLib documents for a GWeakRef that is going away, and the dispose-time
clears now use it. set(null) still works and is unchanged; the clear in
handleReloadConfig stays a set(null) because the object is still alive
there and the reference is reused.

Zig has no destructors so this enforces nothing. It puts the
requirement on the type someone is already looking at.
2026-08-10 15:24:57 +02:00

62 lines
2.2 KiB
Zig

const std = @import("std");
const gtk = @import("gtk");
const gobject = @import("gobject");
/// A lightweight wrapper around gobject.WeakRef to make it type-safe
/// to hold a single type of value.
pub fn WeakRef(comptime T: type) type {
return struct {
const Self = @This();
ref: gobject.WeakRef = std.mem.zeroes(gobject.WeakRef),
pub const empty: Self = .{};
/// Set the weak reference to the given object. This will not
/// increase the reference count of the object.
pub fn set(self: *Self, v_: ?*T) void {
if (v_) |v| {
self.ref.set(v.as(gobject.Object));
} else {
self.ref.set(null);
}
}
/// Release this weak reference.
///
/// You MUST call this before the memory holding this struct is freed,
/// which in practice means from the owner's `dispose`. The target keeps
/// a pointer to this `GWeakRef` so that it can clear it when the target
/// is finalized; if this memory is gone by then, the target walks into
/// whatever now occupies it. That is an invalid read at best, and can
/// hang: the target takes a lock inside each registered weak ref, and
/// reused memory with the low bit set is a lock nothing will release.
///
/// `set(null)` also unregisters and remains valid. This exists so the
/// requirement has a name at the use site rather than looking like an
/// ordinary assignment.
pub fn deinit(self: *Self) void {
self.ref.clear();
}
/// Get a strong reference to the object, or null if the object
/// has been finalized. This increases the reference count by one.
pub fn get(self: *Self) ?*T {
// We can't use `as` because `as` guarantees conversion and
// that can't be statically guaranteed.
return gobject.ext.cast(T, self.ref.get() orelse return null);
}
};
}
test WeakRef {
const testing = std.testing;
var ref: WeakRef(gtk.TextBuffer) = .empty;
const obj: *gtk.TextBuffer = .new(null);
ref.set(obj);
ref.get().?.unref(); // The "?" asserts non-null
obj.unref();
try testing.expect(ref.get() == null);
}