Today marks the first release of gotk3, Conformal’s own GTK3 bindings for Go (available on Github here). These bindings were developed out of a frustration with other GTK bindings for Go either using ancient versions of GTK, not handling memory in a way a Go developer would expect, or simply not working at all on our developers’ OpenBSD and Bitrig machines. gotk3 is Conformal’s response to these issues and attempts to be the best solution for developing new GTK applications with Go.
One of the goals for developing gotk3 was to perform memory management in a very Go-like manner. Like many libraries which must handle memory management manually due to the language they are implemented with, GLib (and GTK which uses it) uses reference counting to determine when an object will never again be used and is ready to be freed, releasing the memory resources it required back to the operating system. However, GLib chooses not to use traditional reference counting, but instead uses a quirky variant called “floating references” to achieve this goal. The rest of this post will cover what floating references do, why they exist, and how gotk3 works around this design to handle memory the way a Go developer expects.
Let’s say that while you are developing a GTK application using the native C API, you create a GtkLabel with a call to gtk_label_new(). This label is created with a reference count of one, but that reference is not a normal reference. Instead, GTK returned an object with a “floating” reference. This means that at the time of initialization, no other objects (read: container widgets) hold a reference to the initialized object. If this label is then added to a container, such as a GtkGrid, the grid will “sink” (remove) the floating reference, changing the object into a traditional reference counted object, but keeping the reference count still at one.
Why is this useful? Consider the following C function in a world where floating references do not exist:
label = gtk_label_new(“a new label”);
Calling this function would leak memory because the code to create the label also never unreferences the label before it leaves scope. When this label is added to the grid container, the reference count would be bumped to two. Later, when gtk_widget_destroy() is called on the label, all references to other widgets will be broken (in this case, only the grid). However, because this call causes only one unreference (for removal from the grid), the reference count would never decrease to zero, and the label would never be freed.
There are two possible ways to fix this issue. The first method is to add an explicit unreference at the end of the above function to drop the reference count back to one since the calling function no longer requires a reference to the label. This is a simple solution and is one that most any C programmer expecting normal reference counting would expect to work properly. The other possibility, and the one chosen by GLib, is to use floating references to cause the first container the object is added to to only sink the floating reference and never modify the reference count. According to the GObject class documentation, this particular implementation was chosen only because it was believed to be a convenience to C programmers to save them an extra function call, rather than making programmers remember to perform an explicit unreference at the end of every function where a GTK widget is created.
However nice this code may be for someone who understands how floating references work, this kind of clever code is often dangerous for those who do not understand the intricacies of GLib’s memory management. Even for a developer who does understand the concept, it still takes a lot of time to go through a code base and fix all the issues related to improper handling of floating references (as was done to Conformal’s browser xombrero recently). As another example, the GTK mailing list archives are filled with posts from users wondering why allocating a widget and then calling gtk_widget_destroy() on it in a loop still ramps up their memory usage as they watch the output of top. These users may expect GTK to handle their memory automatically for them even without ever adding a widget to a container as GTK seems to perform this task fine in some situations where explicit unreferences are missing.
This brings us back to Go, and one of the reasons why Conformal began implementing our own GTK bindings. Go programmers expect objects to be automatically freed by Go’s garbage collector after going out of scope, proving that they will never be used by the program again. This assumption changes when using cgo, however. Special care is needed for this situation as any memory allocated by C function calls must be managed manually. Many other GTK bindings for Go make no attempt to perform any memory management for GTK objects and instead opt to rely solely on GObject’s floating references to perform the final unreference when a widget is removed from a container. As this is in no way different from what native GTK in C does, leaks are inevitable unless a developer is acutely aware of the implementation and its pitfalls.
gotk3 takes a different approach by working as closely as possible with Go’s garbage collector to free any unused objects, even if these objects were never added to a container. When gotk3 creates a new struct representing any object inheriting from GInitiallyUnowned (a GLib base class where all children are initialized with a floating reference), the GObject’s floating reference is immediately sunk, and a runtime finalizer is set on the Go struct to perform an unreference of the GObject when the Go struct goes out of scope.
Due to this design, a Go developer never needs to take any special considerations for whether a GTK widget will be freed or not. If a widget is never added to a container, an unreference will occur during the next garbage collection cycle after the widget is out of scope. If the widget was added to a container and the container was removed, the object could either be freed here or at a future garbage collection cycle depending on whether the finalizer has run yet. In either situation, however, the memory is still eventually freed. If Go holds a persistent reference to a widget (for example, a global variable pointing to the object), the finalizer will never run until this pointer has been set to another value. This last situation can be seen as a limitation of mark-and-sweep garbage collectors (even if a value is logically never going to be used again, but it is in scope, it might be used later and should not be freed). However, this pitfall describes how Go’s garbage collector already works, and because it is familiar to any Go developer, it is the behavior we wish to mimic in our bindings as well.
gotk3 is still a relatively new project and more work must be done to cover all of GTK’s calls and features. However, Conformal believes that we have developed one of the better options for writing modern GUI code in Go, and the only good solution for using GTK. With this initial release Conformal hopes to gather the attention of the open source golang community and gain additional help from users and supporters who hold a clear understanding of the goals and design of this project. If you would like to help out, grab a copy from Github and start hacking away.