Most of the bindings generated by zig-gobject are direct translations of the
source GIR (GObject introspection metadata), adjusted to better fit Zig
conventions (such as camelCase
function names). For example, the function
gtk_application_new
is translated to gtk.Application.new
. However, there are
some additional elements added by the translation process to expose metadata
(such as type inheritance) and make common tasks simpler and safer.
The generated bindings are designed to provide direct transparency to the
underlying library functions. For example, consider the source of
gtk.Application.new
:
extern fn gtk_application_new(p_application_id: ?[*:0]const u8, p_flags: gio.ApplicationFlags) *_Self;
pub const new = gtk_application_new;
The generated binding here is really a binding rather than a wrapper: the
function gtk.Application.new
is exactly the same as gtk_application_new
at
the binary level. This is similar to the philosophy behind zig translate-c
,
but the GIR data offers much greater organization and precision in the result,
such as the use of the correct pointer type ?[*:0]const u8
for the application
ID, rather than the limited [*c]const u8
which zig translate-c
would
produce.
Zig and GObject-based libraries have different naming conventions in some aspects, and Zig also enforces more stringent naming practices in several ways: for example, fields and decls may not have the same name, names may not shadow others in their enclosing scope. The generated bindings rename the original library symbols as follows:
- Namespace names: lowercased (e.g.
Gtk
becomesgtk
). - Type names: remain the same, as GObject and Zig both use
PascalCase
.- Exception: type names which would conflict with one of the built-in
metadata fields are "mangled" by adding a trailing
_
. For example, a type in a library originally namedClass
will be translated asClass_
.
- Exception: type names which would conflict with one of the built-in
metadata fields are "mangled" by adding a trailing
- Function names: translated from
snake_case
intocamelCase
(e.g.signal_connect_data
becomessignalConnectData
). - Constant names: remain the same. Although Zig prefers lowercase constant
names, in contrast to GObject's
SCREAMING_SNAKE_CASE
, uniformly lowercasing constant names would lead to some conflicts (e.g. GDK'sKEY_A
andKEY_a
would become ambiguous). - Enum and bit field (flags) members: remain the same (
snake_case
). - Field names: remain the same, but prefixed with
f_
to avoid potentially conflicting with other declarations on the types. - Parameter names: remain the same, but prefixed with
p_
to avoid potentially shadowing other names in the enclosing scope.
While it is possible for functions in Zig to be called using method call syntax
obj.method()
if method
has the type of obj
as its first parameter, most
methods in this library are conventionally called with the more verbose syntax
Obj.method(obj)
. The primary reason for this is visual consistency between
normal methods, type-safe generated helpers, and extensions:
gtk.Widget.show(win.as(gtk.Widget))
gtk.Button.signals.clicked.connect(button, Data, &handleButtonClicked, data, .{})
gtk.ext.WidgetClass.setTemplateFromSlice(class.as(gtk.Widget.Class), template)
It is also hoped that the @Result
builtin
proposal will be accepted, which
would allow the elimination of redundant information from the as
calls.
It is up to the user to decide when to use this more verbose method call syntax
or the shorter obj.method()
syntax. There are cases where the above reasoning
doesn't apply, and using the shorter syntax is desirable: for example, when
working with Cairo types, which don't have signal handlers or other helpers, it
is much nicer to write cr.moveTo(0, 0)
than cairo.Context.moveTo(cr, 0, 0)
.
Most additional functionality provided by zig-gobject on top of the libraries
being bound is added through extensions. These extensions are not added
directly to the generated bindings; rather, the extensions file for a namespace
is exposed as ext
from the bindings for the namespace. For example, the
extensions for GObject can be accessed through gobject.ext
.
It is conventional for the extensions of a namespace to mirror the structure of
the namespace being extended. For example, the function
glib.ext.Bytes.newFromSlice
is a helper function which creates a glib.Bytes
from a slice of bytes.
GObject is built around an object-oriented type system. zig-gobject exposes metadata about relationships in the type system through a few special members:
fn getGObjectType() gobject.Type
- this is the GObject "get-type" function for a type, returning the registeredgobject.Type
for the type. For example, the C macroGTK_TYPE_APPLICATION
can be expressed asgtk.Application.getGObjectType()
in Zig.const Class: type
- for a class type, this is the associated class struct. For example,GObjectClass
in C is equivalent togobject.Object.Class
in Zig.const Iface: type
- for an interface type, this is the associated interface struct.const Parent: type
- for a class type, this is the parent type. For example,gtk.ApplicationWindow.Parent
is the same asgtk.Window
.const Implements: [_]type
- for a class type, this is an array of all the interface types implemented by the class. For example,gtk.Window.Implements
contains several types, includinggtk.Buildable
.const Prerequisites: [_]type
- for an interface type, this is an array of all the prerequisite types of the interface.virtual_methods
- a namespace containing virtual method metadataproperties
- a namespace containing property metadatasignals
- a namespace containing signal metadata
As an example of how these additional members are useful, the function
gobject.ext.as
casts an object instance to another type, failing to compile if
the correctness of the cast cannot be guaranteed. For example, if win
is a
gtk.Window
, then the call gobject.ext.as(gobject.Object, win)
works, but
gobject.ext.as(gtk.ApplicationWindow, win)
will fail to compile, because win
might not be an instance of gtk.ApplicationWindow
.
Each element in the virtual_methods
namespace has the following structure:
pub fn call(
/// The type struct instance on which to call the method.
class: anytype,
...method parameters...
) ...method return type...
pub fn implement(
/// The type struct instance on which to implement the method.
class: anytype,
/// The implementation of the method.
impl: *const fn(*@typeInfo(@TypeOf(class)).pointer.child.Instance, ...method parameters...) ...method return type...,
)
For example, the virtual method finalize
can be implemented for an object type
using gobject.Object.virtual_methods.finalize.implement
. This offers greater
type safety than casting the type struct instance to an ancestor type and
setting the method field directly.
Each element in the properties
namespace has the following structure:
pub const name = "property";
pub const Type = T;
Each element in the signals
namespace has the following structure:
pub const name = "signal";
pub fn connect(
/// The object to which to connect the signal handler.
obj: anytype,
/// The type of the user data to pass to the handler.
comptime T: type,
/// The signal handler function.
callback: *const fn (@TypeOf(obj), ...signal parameters..., T),
/// User data to pass to the handler.
data: T,
/// Signal connection options.
options: struct { after: bool = false },
)
Using these generated signal connection functions offers greater type safety
than calling gobject.signalConnectData
directly.
Some utility functions are added to the generated types to improve the safety and ease of use of the bindings. These utility functions are chosen judiciously: most additional utility functions are available through extensions rather than translated directly into the bindings, to avoid confusion and collision with the rest of the translated bindings.
fn as(self: *Self, comptime T: type) *T
- for a class, interface, or type struct type, this is a shortcut forgobject.ext.as
, due to its extremely frequent use.fn ref(self: *Self) void
- for a type with a reference function defined in its GIR, or for a type known to extend fromgobject.Object
, this is a function used to increment the object's reference count. This is generated only if the containing type does not already have a member translated asref
.fn unref(self: *Self) void
- likeref
, but decrements the object's reference count.