Vala's owned and unowned keywords: letting the compiler write your refcount calls
Vala's owned and unowned annotations turn GObject reference counting into a compile-time contract. Here's how ownership transfer works, how to inspect the generated C, and where the model breaks down.
17 Aug 2025, 06:12 UTC

If you've ever written GObject code in raw C, you know the drill: every function that takes a GObject* leaves you guessing whether it steals your reference or borrows it. Get it wrong and you leak, or worse, you unref an object someone else still holds. Vala's answer is to make that contract part of the type system. Two keywords — owned and unowned — tell the compiler exactly where references live, and it inserts the g_object_ref/g_object_unref calls for you.
The useful takeaway: in Vala you almost never write refcount code by hand, but you still need to understand ownership transfer to write correct APIs and to bind C libraries safely.
What ownership actually means here
Vala compiles to C and targets the GObject type system, so memory management for class instances is reference counting. Every reference to an object is either owning (it keeps the object alive and must eventually be released) or borrowed (it assumes someone else keeps the object alive).
By default, local variables and return values in Vala are owning references. When an owning variable goes out of scope, the compiler emits the unref. When you pass an object to a function, the default is that the callee borrows it — no refcount change at all.
The two annotations change that default:
ownedon a parameter means the callee takes over the caller's reference. After the call, the caller's variable is conceptually empty; the callee is responsible for releasing it.unownedon a variable, field, or return type means a borrowed reference that never touches the refcount. It's the Vala-level equivalent of "don't unref this, you don't own it."
A worked example
Here's a small illustration using a custom GObject class:
public class Document : Object {
public string title { get; construct set; }
public Document (string title) {
Object (title: title);
}
}
public class Archive : Object {
private Document? current;
// Takes ownership: the caller gives up its reference.
public void store (owned Document doc) {
current = (owned) doc;
}
// Returns a borrowed reference: caller must NOT unref.
public unowned Document? peek () {
return current;
}
}
void main () {
var archive = new Archive ();
var doc = new Document ("notes");
archive.store ((owned) doc);
// 'doc' is now null here; Archive owns the object.
unowned Document? d = archive.peek ();
print ("%s\n", d.title);
}Two things to notice. First, the (owned) doc cast at the call site is a move: ownership transfers into store, and the compiler nulls out doc so you can't use a stale pointer afterward. Assigning one owning variable to another without the cast would instead create a second owning reference (with a ref call) — the compiler keeps both sides consistent either way.
Second, peek returns unowned, which is a promise that the archive outlives the use of the returned reference. If you need the returned document to survive the archive, assign it to a normal (owning) variable and Vala will add the ref for you.
Verifying what the compiler did
You don't have to take this on faith. Vala is a transpiler, so you can inspect the generated C directly:
valac -C ownership-demo.valaRun this in your project directory (no special permissions needed; it just writes a .c file next to your sources). Open the generated file and look at archive_store and main: you should see g_object_unref calls inserted at scope exits and no refcount churn around the peek accessor. This is the fastest way to build intuition for what each annotation costs at runtime. If you want runtime confirmation, a debug build can print doc->ref_count (the field is public in GObject for debugging purposes, though it's not a stable API to build logic on).
Where the model breaks down
The system is pleasant, but it has sharp edges worth knowing before you rely on it.
It only applies to reference-counted types. Structs, enums, and fundamental types like int are copied by value; owned and unowned are meaningless there. Strings and arrays are reference-counted in Vala, so the annotations do apply.
Reference cycles still leak. If object A owns B and B owns A, neither refcount ever reaches zero, and Vala ships no cycle collector. The standard fix is to make one direction a weak reference with the weak keyword (common for parent/child and signal-heavy relationships), which integrates with GObject's weak-pointer mechanism and nulls itself when the target dies.
VAPI bindings must tell the truth. When you bind a C library, the ownership annotations in the .vapi file are how Vala decides where to ref and unref. If the C function steals a reference but the binding doesn't say owned, you leak; if the binding claims ownership the C library never transferred, you get a double-free. When writing bindings, check the C documentation for phrases like "transfer full" / "transfer none" (the GObject introspection convention) and map them to owned and unannotated respectively.
A practical rule of thumb
Default to owning references everywhere and let the compiler do its job. Reach for owned parameters when a function genuinely consumes an object (storing it, handing it to another subsystem), and unowned returns when handing out internal state that the container keeps alive. When you hit a cycle, break the back edge with weak. And whenever you're unsure what the compiler emitted, valac -C turns the whole ownership story into plain C you can read — that five-minute check has settled more refcount debates than any style guide.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.