Choosing Owned, Unowned, or Weak References in Vala to Manage GObject Lifetimes
A decision guide for Vala developers on when to use owned, unowned, or weak references to manage GObject lifetimes, with a concrete parent‑child cycle example and validation steps using valac, Valgrind, and G_DEBUG.
25 Dec 2025, 19:12 UTC

Problem
Vala compiles to C using GObject reference counting. Every class instance starts with a reference count of one. If you keep an owned reference longer than needed, the object never finalizes and memory leaks. If you keep an unowned reference after the owner releases it, you hold a dangling pointer. A weak reference solves cycles but requires a null check before each use. The decision you make at each variable declaration determines whether your program leaks, crashes, or runs correctly.
Decision Matrix
| Reference Kind | Ownership Transfer | Reference Count Effect | Typical Use Case | Risk if Misused |
|---|---|---|---|---|
owned | Yes – caller receives a new strong reference | g_object_ref on assign, g_object_unref on scope exit | Returning a newly created object, storing a child in a container | Leak if the holder never releases; double‑free if two owners unref the same reference |
unowned | No – borrows the existing reference | No change to ref‑count | Iterating a collection, temporary access in a callback | Dangling pointer if the original owner releases before the borrow ends |
weak | No – does not keep the object alive | No change; automatically set to null on finalization | Back‑references that would create a cycle (parent ↔ child), caches | Must check for null before dereferencing; extra indirection |
Trade‑offs
- Owned gives you full control but forces you to balance every
refwith anunref. In a long‑lived container this is the correct default. - Unowned is zero‑cost at runtime, but the compiler cannot verify that the borrowed object outlives the borrow. Use it only when the lifetime is provably shorter (e.g., loop variable over a list that you own).
- Weak adds a small runtime cost (a
GWeakRefinternally) and requires a null check, but it is the only safe way to break a reference cycle without manualunrefgymnastics.
Concrete Example: Parent‑Child Cycle with a Weak Back‑Reference
The following Vala snippet shows a tree node that owns its children, while each child holds a weak reference to its parent. This pattern prevents the classic leak where a parent and child keep each other alive forever.
using GLib;
using GObject;
public class TreeNode : Object {
// Strong ownership of children
public owned List children = new List();
// Weak back‑reference – does not increase parent's ref‑count
public weak TreeNode parent;
public TreeNode () {}
public void add_child (TreeNode child) {
// Transfer ownership of child to this node
children.append (child);
child.parent = this; // weak assignment, no ref‑count change
}
}
void main () {
var root = new TreeNode ();
var leaf = new TreeNode ();
root.add_child (leaf);
// When root goes out of scope, its unref drops the last strong reference.
// leaf.parent is weak, so leaf is also finalized.
}
Key points:
owned Listmakes the list the sole strong owner of each child.weak TreeNode parentdeclares aGWeakRef; the compiler emits a null‑check helper.- No explicit
unrefcalls are needed; scope‑based cleanup handles it.
Validation Steps
After writing the code, verify that no leaks or dangling pointers remain.
- Compile and inspect generated C
Run this in your project directory; no special permissions required. The output is source code you can audit.valac -C --pkg gobject-2.0 tree_node.vala # Examine tree_node.c for g_object_ref / g_object_unref pairs # and GWeakRef usage on the parent field. - Run under Valgrind
Execute from the same directory. Expected result:valac --pkg gobject-2.0 tree_node.vala -o tree_node valgrind --leak-check=full --show-leak-kinds=all ./tree_nodedefinitely lost: 0 bytes. Any non‑zero indicates an ownership mismatch. - Force deterministic finalization
This environment variable makes GObject run finalizers immediately when the last strong reference drops, letting you observe weak references becomingG_DEBUG=gc-friendly ./tree_nodenullin a debugger or via a test harness. - Unit test for cycle breakage
Compile with// test_cycle.vala using GLib; using GObject; public class CycleTest : Object { static int finalized = 0; public weak CycleTest partner; ~CycleTest () { finalized++; } public static int get_finalized () { return finalized; } } void test_cycle () { var a = new CycleTest (); var b = new CycleTest (); a.partner = b; b.partner = a; // Drop external strong refs a = null; b = null; // Force GC cycle GLib.Object.run_dispose (); assert (CycleTest.get_finalized () == 2); } void main () { test_cycle (); }valac --pkg gobject-2.0 test_cycle.vala -o test_cycleand run. The assertion passes only if both objects finalize, confirming the weak links broke the cycle.
Limitations
- Vala’s default toolchain uses GObject reference counting; enabling Boehm GC (
--enable-gc) changes the semantics and is not covered here. - Weak references rely on
GWeakRef, which is only available on GLib ≥ 2.56. Older targets need a manualg_object_weak_refimplementation. - The compiler cannot prove that an
unownedreference’s lifetime is safe; static analysis tools or code review are required.
How to Check the Result in Practice
After each change, run the Valgrind step (2) as part of your CI pipeline. A clean run (zero definitely lost bytes) is the practical gate that the ownership annotations are balanced. Pair it with the unit test (4) to guarantee that weak back‑references actually break cycles.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.