Using Vala's Compact Classes to Avoid GObject Overhead
Learn how Vala’s compact classes eliminate GObject overhead for simple data types while keeping compile‑time ownership and null‑safety.
14 Jul 2025, 03:22 UTC

Problem: Unnecessary GObject baggage in small data types
When building a GNOME‑based utility, you often need lightweight structures that are passed around frequently—think of a simple point or a colour triplet. Declaring them as class MyPoint : GLib.Object brings in the full GObject machinery: reference counting, type registration, and signal/property support that you never use. This adds both runtime overhead and boilerplate.
Thesis: Vala’s compact classes give you the safety of ownership analysis without the GObject cost
By defining a type as a compact class, Vala omits the GLib.Object base class while still providing compile‑time ownership tracking, nullable‑type safety, and support for inheritance and interfaces. The generated C code contains only a plain struct and explicit g_free calls where ownership ends, eliminating reference‑counting overhead.
Worked example: A colour struct used in a drawing loop
// colour.vala
using GLib;
compact class Colour {
public double r, g, b, a;
public Colour (double r, double g, double b, double a = 1.0) {
this.r = r; this.g = g; this.b = b; this.a = a;
}
// Example method that returns a new owned instance
public Colour brighter (double factor) {
return new Colour (Math.min (r * factor, 1.0),
Math.min (g * factor, 1.0),
Math.min (b * factor, 1.0),
a);
}
}
void main () {
var base = new Colour (0.2, 0.4, 0.6);
var bright = base.brighter (1.5); // owned, will be freed automatically
// bright is used here; when it goes out of scope Vala inserts g_free
printf ('Bright colour: %f %f %f\n', bright.r, bright.g, bright.b);
}
To see what Vala emits, compile with the C‑code dump:
valac --ccode --pkg glib-2.0 colour.vala
The generated colour.c contains:
typedef struct _Colour Colour;
struct _Colour {
double r;
double g;
double b;
double a;
};
static inline Colour*
colour_new (double r, double g, double b, double a)
{
Colour* self = g_malloc0 (sizeof (Colour));
self->r = r; self->g = g; self->b = b; self->a = a;
return self;
}
static void
colour_free (Colour* self)
{
g_free (self);
}
/* ... brighter implementation returns a new Colour ... */
int main (int argc, char** argv) {
Colour* base = colour_new (0.2, 0.4, 0.6, 1.0);
Colour* bright = colour_brighter (base, 1.5);
g_print ("Bright colour: %f %f %f\n", bright->r, bright->g, bright->b);
colour_free (bright); // inserted by Vala when bright leaves scope
colour_free (base);
return 0;
}
Notice there are no g_object_ref or g_object_unref calls—only a single g_malloc0 and matching g_free. This confirms the zero‑overhead claim for this type.
Trade‑off: No GObject introspection, signals, or properties
Because a compact class does not derive from GLib.Object, it cannot:
- Expose
GObjectproperties that language bindings (e.g., PyGObject, JavaScript) can introspect. - Emit or connect to signals.
- Participate in the GType system beyond being a plain struct.
If you need any of those features—say, a widget that must be accessible from a UI builder—you must fall back to a regular class that inherits from GLib.Object and accept the reference‑counting overhead.
Actionable closing: Adopt compact classes for pure‑data types
- Identify struct‑like types in your codebase that never need signals, properties, or introspection.
- Replace
class Foo : GLib.Objectwithcompact class Foo. - Use nullable types (
Foo?) where appropriate to keep null‑safety at compile time. - Verify the generated C with
valac --ccodeto ensure onlyg_malloc0/g_freeappear. - Run the binary under
valgrindor withG_SLICE=always-mallocto confirm no leaks or double‑frees.
When the type later gains a requirement for GObject features, revert to a full class; otherwise, compact classes give you the safety of Vala’s ownership model without the runtime cost.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.