tl;dr Don’t put any code in your foo_label_new()
function other than g_object_new()
, and watch out with Vala.
From this GJS bug report I realized there’s a trap that GObject library writers can fall into,
that I don’t think is documented anywhere. So I’m writing a blog post about it. I hope readers from Planet GNOME can help figure out where it needs to be documented.
For an object (let’s call it FooLabel) that’s part of the public API of a library (let’s call it libfoo), creating the object via its foo_label_new()
constructor function should be equivalent to creating it via g_object_new()
.
If foo_label_new()
takes no arguments then it should literally be only this:
FooLabel * foo_label_new(void) { return g_object_new(FOO_TYPE_LABEL, NULL); }
If it does take arguments, then they should correspond to construct properties, and they should get set in the g_object_new()
call. (It’s customary to at least put all construct-only properties as arguments to the constructor function.) For example:
FooLabel * foo_label_new(const char *text) { return g_object_new(FOO_TYPE_LABEL, "text", text, NULL); }
Do not put any other code in foo_label_new()
. That is, don’t do this:
FooLabel * foo_label_new(void) { FooLabel *retval = g_object_new(FOO_TYPE_LABEL, NULL); retval->priv->some_variable = 5; /* Don't do this! */ return retval; }
The reason for that is because callers of your library will expect to be able to create FooLabels using g_object_new()
in many situations. This is done when creating a FooLabel in JS and Python, but also when creating one from a Glade file, and also in plain old C when you need to set construct properties. In all those situations, the private field some_variable
will not get initialized to 5!
Instead, put the code in foo_label_init()
. That way, it will be executed regardless of how the object is constructed. And if you need to write code in the constructor that depends on construct properties that have been set, use the constructed
virtual function. There’s a code example here.
If you want more details about what function is called when, Allison Lortie has a really useful blog post.
This trap can be easy to fall into in Vala. Using a construct block is the right way to do it:
namespace Foo { public class Label : GLib.Object { private int some_variable; construct { some_variable = 5; } } }
This is the wrong way to do it:
namespace Foo { public class Label : GLib.Object { private int some_variable; public Label() { some_variable = 5; // Don't do this! } } }
This is tricky because the wrong way seems like the most obvious way to me!
This has been a public service announcement for the GNOME community, but here’s where you come in! Please help figure out where this should be documented, and whether it’s possible to enforce it through automated tools.
For example, the Writing Bindable APIs page seems like a good place to warn about it, and I’ve already added it there. But this should probably go into Vala documentation in the appropriate place. I have no idea if this is a problem with Rust’s gobject_gen!
macro, but if it is then it should be documented as well.
Documented pitfalls are better than undocumented pitfalls, but removing the pitfall altogether is better. Is there a way we can check this automatically?