Skip to content

Latest commit

 

History

History
195 lines (159 loc) · 8.72 KB

binding-strategy.md

File metadata and controls

195 lines (159 loc) · 8.72 KB

Binding strategy

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.

Naming

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 becomes gtk).
  • 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 named Class will be translated as Class_.
  • Function names: translated from snake_case into camelCase (e.g. signal_connect_data becomes signalConnectData).
  • 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's KEY_A and KEY_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.

Usage conventions

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).

Extensions

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.

Type system metadata

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 registered gobject.Type for the type. For example, the C macro GTK_TYPE_APPLICATION can be expressed as gtk.Application.getGObjectType() in Zig.
  • const Class: type - for a class type, this is the associated class struct. For example, GObjectClass in C is equivalent to gobject.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 as gtk.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, including gtk.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 metadata
  • properties - a namespace containing property metadata
  • signals - 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.

Virtual methods

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.

Properties

Each element in the properties namespace has the following structure:

pub const name = "property";

pub const Type = T;

Signals

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.

Utility functions

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 for gobject.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 from gobject.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 as ref.
  • fn unref(self: *Self) void - like ref, but decrements the object's reference count.