Introduction

gir is a tool to automatically generate safe wrappers for a C library with GObject introspection information. In order to use it you need the .gir file containing the introspection data for the library you want to create the bindings for, as well as the .gir files for all its dependencies. Have a look at the tutorial if you don't know how to find the .gir files. If your library does not provide a .gir file, unfortunately you cannot use gir, but maybe you can try rust-bindgen.

This book contains a tutorial on how to use gir. As an example we will create the bindings for Pango. In many cases you will be able to follow the same steps with your library. If you are already familiar with gir and you just want to look up details about the configuration files, feel free to skip ahead to the documentation of the configuration files.

General steps

gir tries to make it as simple as possible to generate a safe wrapper for your C library. The process can be divided into four steps that correspond to the four operating modes gir has.

  • Generating unsafe bindings: In this step, the low-level FFI bindings are created from the supplied *.gir files. These are essentially direct calls into the related C library and are unsafe. The resulting crate is typically appended with -sys. The operating mode is sys.

  • Generating a safe wrapper: Next, another crate for a layer on top of these unsafe (sys) bindings is created, which makes them safe for use in general Rust. The operating mode is normal.

  • Checking for missing types/methods: The operating mode not_bound allows you to see the detected types/methods that will not be generated for whatever reasons.

  • Adding documentation: After the safe wrapper is created, gir can even generate the documentation for us. Use the operating mode doc to do so.

Regenerating the bindings and wrapper

In order to generate the bindings and the wrapper for the first time, the above-mentioned steps should be followed. When you want to regenerate the crates because e.g. the library was updated, you can simplify the process by running the helper script ./generator.py. The script detects Gir.toml configurations in the current directory and subdirectories (or the paths passed on the command-line) and generates "normal" or "sys" crates for it. Alternatively --embed-docs can be passed to prepare source-code for a documentation built by moving all documentation into it. For a complete overview of available options, pass --help.

GIR format reference

It can always be useful to look at the reference or schema.

Contact us

If you use gir on another library and it fails and you can't figure out why, don't hesitate to contact us!

Tutorial

In this tutorial, we go through the basic steps needed to generate a safe wrapper for a simple C library. We start with finding the .gir files needed and end with generating the documentation. We also look at a few common errors and how to fix them. Because this can be a bit abstract sometimes, we use the creation of the bindings for Pango as an example. The example continues through all of the chapters of the tutorial. If you follow along until the end, you will have generated a safe wrapper including the documentation. In case you are stuck at any point or there are other errors and you can't figure out what's going on, don't hesitate to reach us so we can give you a hand!

Let's dive right into it and let's set up our project folder!

Preparation

In order to install gir and nicely structure the project, there are a few things to set up.

Set up the project folder

In order to keep the project folder nicely organized, let's create a folder where we will work in and initialize the repo. We will create two library crates. pango will contain the safe wrapper crate and because it is a wrapper for the unsafe bindings, we create the pango-sys crate within the pango crate. If no Cargo.toml file is present in the sys create, a new one will be generated, so let's be safe and delete the automatically created file before we begin. The following commands will set up the project folder as described.

mkdir gir-tutorial
cd gir-tutorial/
git init
cargo new pango --lib
cd pango
cargo new pango-sys --lib
rm pango-sys/Cargo.toml

We will also create a file called "Gir.toml" in each of the crates.

touch Gir.toml
touch pango-sys/Gir.toml
cd ..

Installing gir

Of course we also need to download and install gir.

git submodule add https://github.com/gtk-rs/gir
git config -f .gitmodules submodule.gir.update none
git submodule set-branch --branch master -- ./gir
cd gir
cargo install --path .
cd ..

By adding it as a submodule, we are able to fetch future updates of the tool and we always exactly know which gir version we used to generate our bindings. We also change the setting so that the submodule is not automatically checked out, otherwise anyone using your library from git will have the useless submodule checked out. Run git submodule update --checkout if you want to update the submodule. Then we set the branch of the submodule to master.

If there are any updates to gir in the future, we can install them by opening our project folder gir-tutorial and running

git submodule update --remote
cd gir
cargo install --path .
cd ..

Summary

You should now have a folder looking like this:

gir
  |
  |---- ...
pango/
  |
  |---- Cargo.toml
  |---- Gir.toml
  |---- pango-sys/
  |       |
  |       |---- Gir.toml
  |       |---- src/
  |              |
  |              |---- lib.rs
  |---- src/
          |
          |---- lib.rs
.git
  |
  |---- ...
.gitmodules

Now that we installed gir and prepared our project folder, let's get the .gir files.

Where can I find those .gir files?

There are multiple ways you can get the needed .gir files. The *.gir you need corresponds to the library you want to generate bindings for. If you have the library installed, you can search for the .gir file under /usr/share/gir-1.0/. Having the library installed is a good idea so that you can test the generated code. Otherwise you should be able to get it from the package that installs the library. Ubuntu for example allows you to search for packages and download them via their website. You can copy the .gir file of your library to the root of your project folder. You don't have to store all .gir files in the same folder. You can add multiple paths by changing the girs_directories field in the Gir.toml files. More on this in the next chapters.

Have a look at the .gir file of your library. At the beginning of the file, you probably see something similar to <include name="GObject" version="2.0"/>. "GObject" in this case would be a dependency and you will have to find the .gir file for your dependencies as well. In most cases it will be enough to follow the next two steps of the tutorial to get all needed files.

GTK dependencies

If your library depends on GTK libraries, the recommended way to get the .gir files for them is to add the gir-files repo as a submodule as well. It's the recommended way, because some of the .gir files included in the libraries are invalid (missing or invalid annotations for example). These errors are already fixed in the gir files from the repo. Otherwise you could use the above-mentioned methods to find the files and run the script to fix the .gir files available in the gir-files repository (and only them!). You can run it like this (at the same level of the .gir files you want to patch):

sh fix.sh

GStreamer dependencies

For GStreamer related dependencies, follow the above-mentioned steps but add this repo instead.

Other dependencies

If you have other dependencies, you have to find the files yourself. They can often be found in the repo containing the source of your dependencies or if you have them installed, you might find them under /usr/share/gir-1.0/ again.

Example

We want to generate the wrapper for pango. It is related to GTK, so in order to get its .gir files, we use the recommended way. While being in the project folder git-tutorial, we add the gir-files repo as a submodule and set the branch of the submodule to master.

git submodule add https://github.com/gtk-rs/gir-files
git config -f .gitmodules submodule.gir-files.update none
git submodule set-branch --branch master -- ./gir-files

We also change the setting so that the submodule is not automatically checked out, otherwise anyone using your library from git will have the useless submodule checked out. Run git submodule update --checkout if you want to update the submodule. If you look into gir-files, you'll see a file named Pango-1.0.gir. That's the one for pango. Because we already added the gir-files repo, we also have all the other .gir files of the dependencies that we need. Now we can create the unsafe bindings.

Generating the FFI library

We have installed gir, set up our repo and have all the .gir files we need. Now let's work on the unsafe bindings of the -sys crate. Let's change into the directory of the sys crate.

cd pango/pango-sys

The Gir.toml file

The first step is to let gir know what to generate. This is what the Gir.toml file that we created inside the pango-sys folder is for. This file will not be replaced when we run gir again. The file is currently empty so let's add the following to it:

[options]
library = "Pango"
version = "1.0"
min_cfg_version = "1.0"
target_path = "."
girs_directories = ["../../gir-files/"]
work_mode = "sys"
single_version_file = true
  • library stands for the name of the library we want to generate.
  • version stands for the version of the library to be used.
  • min_cfg_version will be the minimum version supported by the generated bindings.
  • target_path stands for the location where the files will be generated.
  • girs_directories stands for the location of the .gir files.
  • work_mode stands for the mode gir is using. The options here are sys and normal.
  • single_version_file is a very useful option when you have a lot of generated files (like we'll have). Instead of generating the gir hash commit used for the generation in the header of all generated files, it'll just write it inside one file, removing git diff noise a lot.

You can find out the values for library and version by looking at the name of the .gir file of your library. In our case it is called Pango-1.0.gir. This tells us that the library is Pango and the version is 1.0. If you don't know what value to use for min_cfg_version, use the same as you use for version. If not all needed .gir files reside in ../../gir-files/, you can add the path to the other files by changing girs_directories. If for example you also have .gir files in the root of your project folder, change it to girs_directories = ["../../gir-files/", "../.."]. Because we are generating the unsafe bindings, we use the sys work mode.

Let's generate the sys crate now:

gir -o .

You should now see new files and a new folder.

  • build.rs
  • Cargo.toml
  • Gir.toml
  • src/lib.rs
  • tests/

Now let's try to build it:

cargo build

Surprise. It doesn't build at all and you should see a lot of errors. Well, that was expected. We need to add some dependencies in order to make it work. Have a look at the errors of the compiler to find out which are missing. In our example, the compiler throws the following errors:

#![allow(unused)]
fn main() {
use of undeclared crate or module `glib`
use of undeclared crate or module `gobject`
}

The dependencies need to be added to the external_libraries part. The names of the dependencies are the same as in the .gir files and not what the packages to install the libraries might be called. The compiler told us that the GLib and the GObject dependencies are missing. Let's update our Gir.toml file to fix it:

[options]
library = "Pango"
version = "1.0"
min_cfg_version = "1.0"
target_path = "."
girs_directories = ["../../gir-files/"]
work_mode = "sys"

external_libraries = [
    "GLib",
    "GObject",
]

If one of your .gir files changed or you want to use an updated version of gir to generate the code, there is no need to delete the Cargo.toml or the Cargo.lock files before you regenerate the code. Because we made some changes to the Gir.toml file, we have to run gir again. Changing the content of the external_libraries array means that additional dependencies have to be added. gir does this automatically for you, but only if there is no Cargo.toml and no Cargo.lock file present. Just remove the Cargo.* files and run gir again and the additional dependencies will be added. If you made any manual changes to the file, you would have to do these changes again. After regenerating the code, we build the crate to see if the errors are gone.

rm Cargo.*
gir -o .
cargo build

When executing the above commands, there should not be any errors and everything should work fine. Just to be sure everything was correctly generated, we can run some tests (graciously generated by gir as well):

cargo test

Normally, all tests passed. If you get an error when running those tests, it's very likely that the sys generation is invalid and/or incomplete.

The Gir.toml file

This file was automatically generated but it will not be replaced when we run gir again. Make sure to look at your Cargo.toml to optionally add more information to it. If there are any [features], you should try building and testing with these features activated as well. If you'd like, you can also set the default features. This can be useful for example if you want to always activate the newest version unless the user of the crate specifies an older version.

For our example, we now have a working sys crate containing all functions and objects definition. We are done here and can go back to the folder of the wrapper crate:

cd ..

Time to generate the high-level Rust API!

Generating the Rust API

In the previous step we successfully created the unsafe bindings of the -sys crate. We are now in the directory of the safe wrapper crate (gir-tutorial/pango).

The Cargo.toml file

The Cargo.toml file will not be replaced when you run gir. So it is our responsibility to make sure the information in it is correct. Open the Cargo.toml file and have a look at it. Make sure everything under [package] is to your liking.

Add the following lines to the file:

[package.metadata.docs.rs]
all-features = true
# For build.rs scripts
rustc-args = ["--cfg", "docsrs"]
# For rustdoc
rustdoc-args = ["--cfg", "docsrs"]

This automatically activates the docsrs attribute if you chose to publish the bindings and docs.rs tries to build the documentation. With the docsrs attribute, the crate skips linking the C libraries and allows docs.rs to build the documentation without having the underlying libraries installed. Even if you don't plan to publish it, this line is not going to hurt.

We also need to add libc, bitflags, glib and glib-sys and all other dependencies we used in the sys crate as dependencies. Because we are creating a wrapper for the sys crate, which we generated in the previous chapter, we also need to add the sys crate to the list of dependencies. In the automatically generated code, the sys crate is always called ffi, so we need to rename the sys crate in our Cargo.toml. For our example, this results in the following dependencies:

[dependencies]
libc = "0.2"
bitflags = "2.2"

[dependencies.ffi]
package = "pango-sys"
path = "./pango-sys"

[dependencies.glib]
package = "glib-sys"
git = "https://github.com/gtk-rs/gtk-rs-core"

[dependencies.gobject]
package = "gobject-sys"
git = "https://github.com/gtk-rs/gtk-rs-core"

In order to make the features of the sys crate available for users of your safe wrapper, you need to add features. Copy the [features] part of the Cargo.toml of your sys crate and paste it into the Cargo.toml of the normal crate. The features are supposed to activate the corresponding features of the sys crate, so you need to make some changes. If for example you have the following sys features:

[features]
v1_2 = []
v1_4 = ["v1_2"]
v1_6 = ["v1_4"]
v1_8 = ["v1_6"]
v1_10 = ["v1_8"]
v1_12 = ["v1_10"]
v1_14 = ["v1_12"]
v1_16 = ["v1_14"]
v1_18 = ["v1_16"]
v1_20 = ["v1_18"]
v1_22 = ["v1_20"]
v1_24 = ["v1_22"]
v1_26 = ["v1_24"]
v1_30 = ["v1_26"]
v1_31 = ["v1_30"]
v1_32 = ["v1_31"]
v1_32_4 = ["v1_32"]
v1_34 = ["v1_32_4"]
v1_36_7 = ["v1_34"]
v1_38 = ["v1_36_7"]
v1_42 = ["v1_38"]
v1_44 = ["v1_42"]
v1_46 = ["v1_44"]
v1_48 = ["v1_46"]
v1_50 = ["v1_48"]
v1_52 = ["v1_50"]

You need to change the features in the Cargo.toml of your normal crate to

[features]
v1_2 = ["ffi/v1_2"]
v1_4 = ["ffi/v1_4", "v1_2"]
v1_6 = ["ffi/v1_6", "v1_4"]
v1_8 = ["ffi/v1_8", "v1_6"]
v1_10 = ["ffi/v1_10", "v1_8"]
v1_12 = ["ffi/v1_12", "v1_10"]
v1_14 = ["ffi/v1_14", "v1_12"]
v1_16 = ["ffi/v1_16", "v1_14"]
v1_18 = ["ffi/v1_18", "v1_16"]
v1_20 = ["ffi/v1_20", "v1_18"]
v1_22 = ["ffi/v1_22", "v1_20"]
v1_24 = ["ffi/v1_24", "v1_22"]
v1_26 = ["ffi/v1_26", "v1_24"]
v1_30 = ["ffi/v1_30", "v1_26"]
v1_31 = ["ffi/v1_31", "v1_30"]
v1_32 = ["ffi/v1_32", "v1_31"]
v1_32_4 = ["ffi/v1_32_4", "v1_32"]
v1_34 = ["ffi/v1_34", "v1_32_4"]
v1_36_7 = ["ffi/v1_36_7", "v1_34"]
v1_38 = ["ffi/v1_38", "v1_36_7"]
v1_42 = ["ffi/v1_42", "v1_38"]
v1_44 = ["ffi/v1_44", "v1_42"]
v1_46 = ["ffi/v1_46", "v1_44"]
v1_48 = ["ffi/v1_48", "v1_46"]
v1_50 = ["ffi/v1_50", "v1_48"]
v1_52 = ["ffi/v1_52", "v1_50"]

The lib.rs file

The lib.rs file will not be replaced when you run gir. All the code that gir will generate for us is going to be in src/auto. We need to include all auto files in our library. To do so, let's update the src/lib.rs file as follows:

#![allow(unused)]
#![cfg_attr(docsrs, feature(doc_cfg))]

fn main() {
pub use auto::*;
mod auto;
}

The Gir.toml file

As you certainly guessed, we have to fill our Gir.toml file for the normal crate as well. Let's write it:

[options]
library = "Pango"
version = "1.0"
min_cfg_version = "1.0"
target_path = "."
girs_directories = ["../gir-files"]
work_mode = "normal"
single_version_file = true
generate_safety_asserts = true
deprecate_by_min_version = true

generate = []

manual = []

Many of these options look familiar from the last chapter but there are also a few new things in here. Let's take a look at them:

  • work_mode value is now set to normal, it means it'll generate the high-level Rust api instead of the sys-level.
  • generate_safety_asserts is used to generates checks to ensure that, or any other kind of initialization needed before being able to use the library.
  • deprecate_by_min_version is used to generate a Rust "#[deprecated]" attribute based on the deprecation information provided by the .gir file.
  • generate = []: this line currently does nothing. We say to gir to generate nothing. We'll fill it later on.
  • manual = []: this line currently does nothing. We can let gir know about objects which it does not have to generate code for.

Let's make a first generation of our high-level Rust API!

gir -o .

If you take a look at which files and folders were created, you'll see a new "auto" folder inside "src". This folder contains all the generated code. It doesn't contain anything though. Which makes sense since we're generating nothing.

Now it's time to introduce you to a whole new gir mode: not_bound. Let's give it a try:

> gir -o . -m not_bound
[NOT GENERATED] Pango.Glyph
[NOT GENERATED] Pango.GlyphUnit
[NOT GENERATED] Pango.GlyphItem
[NOT GENERATED] Pango.LayoutRun
[NOT GENERATED] Pango.Alignment
[NOT GENERATED] Pango.Font
[NOT GENERATED] Pango.Language
[NOT GENERATED] Pango.Analysis
[NOT GENERATED] Pango.AttrType
[NOT GENERATED] Pango.Attribute
[NOT GENERATED] Pango.Color
[NOT GENERATED] Pango.AttrColor
[NOT GENERATED] Pango.AttrFloat
[NOT GENERATED] Pango.FontDescription
[NOT GENERATED] Pango.AttrFontDesc
[NOT GENERATED] Pango.AttrFontFeatures
[NOT GENERATED] Pango.AttrInt
[NOT GENERATED] Pango.AttrIterator
[NOT GENERATED] Pango.AttrLanguage
[NOT GENERATED] Pango.AttrList
[NOT GENERATED] Pango.Rectangle
[NOT GENERATED] Pango.AttrShape
[NOT GENERATED] Pango.AttrSize
[NOT GENERATED] Pango.AttrString
[NOT GENERATED] Pango.BaselineShift
[NOT GENERATED] Pango.BidiType (deprecated in 1.44)
[NOT GENERATED] Pango.Context
[NOT GENERATED] Pango.Direction
[NOT GENERATED] Pango.Gravity
[NOT GENERATED] Pango.FontMap
[NOT GENERATED] Pango.GravityHint
[NOT GENERATED] Pango.Matrix
[NOT GENERATED] Pango.FontMetrics
[NOT GENERATED] Pango.FontFamily
[NOT GENERATED] Pango.Fontset
[NOT GENERATED] Pango.Coverage
[NOT GENERATED] Pango.CoverageLevel
[NOT GENERATED] Pango.EllipsizeMode
[NOT GENERATED] Pango.FontFace
[NOT GENERATED] Pango.FontMask
[NOT GENERATED] Pango.Stretch
[NOT GENERATED] Pango.Style
[NOT GENERATED] Pango.Variant
[NOT GENERATED] Pango.Weight
[NOT GENERATED] Pango.FontScale
[NOT GENERATED] Pango.FontsetSimple
[NOT GENERATED PARENT] Pango.Fontset
[NOT GENERATED] Pango.GlyphGeometry
[NOT GENERATED] Pango.GlyphVisAttr
[NOT GENERATED] Pango.GlyphInfo
[NOT GENERATED] Pango.Item
[NOT GENERATED] Pango.GlyphString
[NOT GENERATED] Pango.LogAttr
[NOT GENERATED] Pango.GlyphItemIter
[NOT GENERATED] Pango.Script
[NOT GENERATED] Pango.Layout
[NOT GENERATED] Pango.LayoutDeserializeFlags
[NOT GENERATED] Pango.LayoutIter
[NOT GENERATED] Pango.LayoutLine
[NOT GENERATED] Pango.TabArray
[NOT GENERATED] Pango.WrapMode
[NOT GENERATED] Pango.LayoutSerializeFlags
[NOT GENERATED] Pango.LayoutDeserializeError
[NOT GENERATED] Pango.Overline
[NOT GENERATED] Pango.RenderPart
[NOT GENERATED] Pango.Renderer
[NOT GENERATED] Pango.Underline
[NOT GENERATED] Pango.ScriptIter
[NOT GENERATED] Pango.ShapeFlags
[NOT GENERATED] Pango.ShowFlags
[NOT GENERATED] Pango.TabAlign
[NOT GENERATED] Pango.TextTransform
[NOT GENERATED FUNCTION] Pango.attr_allow_breaks_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_background_alpha_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_background_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_baseline_shift_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_break because of Pango.AttrList and Pango.LogAttr
[NOT GENERATED FUNCTION] Pango.attr_fallback_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_family_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_font_scale_new because of Pango.FontScale and Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_foreground_alpha_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_foreground_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_gravity_hint_new because of Pango.GravityHint and Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_gravity_new because of Pango.Gravity and Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_insert_hyphens_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_letter_spacing_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_line_height_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_line_height_new_absolute because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_overline_color_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_overline_new because of Pango.Overline and Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_rise_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_scale_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_sentence_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_show_new because of Pango.ShowFlags and Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_stretch_new because of Pango.Stretch and Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_strikethrough_color_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_strikethrough_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_style_new because of Pango.Style and Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_text_transform_new because of Pango.TextTransform and Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_underline_color_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_underline_new because of Pango.Underline and Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_variant_new because of Pango.Variant and Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_weight_new because of Pango.Weight and Pango.Attribute
[NOT GENERATED FUNCTION] Pango.attr_word_new because of Pango.Attribute
[NOT GENERATED FUNCTION] Pango.break (deprecated in 1.44) because of Pango.Analysis and Pango.LogAttr
[NOT GENERATED FUNCTION] Pango.default_break because of Pango.Analysis and Pango.LogAttr
[NOT GENERATED FUNCTION] Pango.extents_to_pixels because of Pango.Rectangle and Pango.Rectangle
[NOT GENERATED FUNCTION] Pango.find_base_dir because of Pango.Direction
[NOT GENERATED FUNCTION] Pango.get_log_attrs because of Pango.Language and Pango.LogAttr
[NOT GENERATED FUNCTION] Pango.itemize because of Pango.Context, Pango.AttrList, Pango.AttrIterator and Pango.Item
[NOT GENERATED FUNCTION] Pango.itemize_with_base_dir because of Pango.Context, Pango.Direction, Pango.AttrList, Pango.AttrIterator and Pango.Item
[NOT GENERATED FUNCTION] Pango.log2vis_get_embedding_levels because of Pango.Direction
[NOT GENERATED FUNCTION] Pango.markup_parser_finish because of GLib.MarkupParseContext, Pango.AttrList and GLib.Error
[NOT GENERATED FUNCTION] Pango.markup_parser_new because of GLib.MarkupParseContext
[NOT GENERATED FUNCTION] Pango.parse_markup because of Pango.AttrList and GLib.Error
[NOT GENERATED FUNCTION] Pango.parse_stretch because of Pango.Stretch
[NOT GENERATED FUNCTION] Pango.parse_style because of Pango.Style
[NOT GENERATED FUNCTION] Pango.parse_variant because of Pango.Variant
[NOT GENERATED FUNCTION] Pango.parse_weight because of Pango.Weight
[NOT GENERATED FUNCTION] Pango.read_line (deprecated in 1.38) because of GLib.String
[NOT GENERATED FUNCTION] Pango.reorder_items because of Pango.Item and Pango.Item
[NOT GENERATED FUNCTION] Pango.scan_string (deprecated in 1.38) because of GLib.String
[NOT GENERATED FUNCTION] Pango.scan_word (deprecated in 1.38) because of GLib.String
[NOT GENERATED FUNCTION] Pango.shape because of Pango.Analysis and Pango.GlyphString
[NOT GENERATED FUNCTION] Pango.shape_full because of Pango.Analysis and Pango.GlyphString
[NOT GENERATED FUNCTION] Pango.shape_item because of Pango.Item, Pango.LogAttr, Pango.GlyphString and Pango.ShapeFlags
[NOT GENERATED FUNCTION] Pango.shape_with_flags because of Pango.Analysis, Pango.GlyphString and Pango.ShapeFlags
[NOT GENERATED FUNCTION] Pango.tailor_break because of Pango.Analysis and Pango.LogAttr
[NOT GENERATED FUNCTION] Pango.unichar_direction because of Pango.Direction

We now have the list of all the not-yet generated items. Quite convenient. There can be different kinds of not generated items:

  • [NOT GENERATED]: Objects marked with [NOT GENERATED] are objects that we can generate, but we did not (yet) add to the generate array.
  • [NOT GENERATED PARENT]: These objects live in a dependency of the current library. These are the objects we will add to the manual array in the following steps.
  • [NOT GENERATED FUNCTION]: These are global functions that were not generated. To fix it, we just add "NameOfYourLibrary.*" to the generate array in the Git.toml and add the following line to your src/lib.rs file:
#![allow(unused)]
fn main() {
pub mod functions {
  pub use super::auto::functions::*;
}
}

Generating the code

In order to generate the code for the safe wrapper, we follow these steps until all objects have been generated:

  • Run gir -o . -m not_bound to see which objects have not been generated yet
  • Pick one of the types marked with [NOT GENERATED]
  • Add it to the generate array in the Gir.toml file
  • Run gir -o . to generate the code
  • Open the generated files under src/auto and have a look at them
  • Search for /*Ignored*/. If the type name following /*Ignored*/ is prepended by [crate_name]:: (e.g Ignored*/&glib::MarkupParseContext),
    • then we add it to the manual array. By doing so we tell gir that those types have been generated somewhere else and that they can be used just like the other types.
    • Otherwise, the type comes from the current crate and we just put it into the generate list of the Gir.toml file.
  • Start with the first step again

The names of the objects are not the same as the crates names. You have to use the names of the corresponding gir files.

Okay, let's go through that process for a few objects of our example.

🚧 TODO: Add remaining steps of the pango example 🚧

Again, if you do it on another library and it fails and you can't figure out why, don't hesitate to contact us!

At this point, you should have almost everything you need. Let's have a look at errors that can happen in this process.

Handling generation errors

Luckily there are only a few errors which can happen with gir generation. Let's take a look at them.

Cannot find macros

Compilation of the generated bindings may fail with errors like the following:

error: cannot find macro `skip_assert_initialized` in this scope
  --> src/auto/enums.rs:83:9
   |
83 |         skip_assert_initialized!();
   |         ^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: have you added the `#[macro_use]` on the module/import?

error: cannot find macro `assert_initialized_main_thread` in this scope
  --> src/auto/example.rs:33:9
   |
33 |         assert_initialized_main_thread!();
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: have you added the `#[macro_use]` on the module/import?

In this case you’ll have to implement them yourself. Macros are order-dependent and you must insert this code before declaring modules that use it (e.g. mod auto). For example, you can add the following to your lib.rs file:

#![allow(unused)]
fn main() {
/// No-op.
macro_rules! skip_assert_initialized {
    () => {};
}

/// Asserts that this is the main thread and either `gdk::init` or `gtk::init` has been called.
macro_rules! assert_initialized_main_thread {
    () => {
        if !::gtk::is_initialized_main_thread() {
            if ::gtk::is_initialized() {
                panic!("GTK may only be used from the main thread.");
            } else {
                panic!("GTK has not been initialized. Call `gtk::init` first.");
            }
        }
    };
}
}

One complication here is that the assert_initialized_main_thread! macro depends on the exact library. If it's GTK-based then the above macro is likely correct, unless the library has its own initialization function. If it has its own initialization function it would need to be handled in addition to GTK's here in the same way.

For non-GTK-based libraries the following macro would handle the initialization function of that library in the same way, or if there is none it would simply do nothing:

#![allow(unused)]
fn main() {
/// No-op.
macro_rules! assert_initialized_main_thread {
    () => {};
}
}

Missing memory management functions

If gir generation fails (for whatever reason), it means you'll have to implement the type yourself. Just like types from other gtk-rs crates, you'll need to put it into the "manual" list. Then you need to put the type into the src folder (or inside a subfolder, you know how Rust works).

/!\ Don't forget to reexport the type inside your src/lib.rs file. For example, let's take a look at the requisition.rs file from the gtk3 crate.

Since it's a "simple" type (no pointer, therefore no memory management to do), gir doesn't know how to generate it. You'll need to implement some traits by hand like ToGlibPtr or ToGlibPtrMut (depending on your needs).

Bad function generation

In some cases, the generated code isn't correct (array parameters are often an issue). In such cases, it's better to just make the implementation yourself. As an example, let's say you want to implement Region::is_empty yourself. A few changes have to be made. Let's start with Gir.toml:

generate = [
    "GtkSource.Language",
]

[[object]]
name = "GtkSource.Region"
status = "generate"
    [[object.function]]
    name = "is_empty"
    ignore = true

So to sum up what was written above: we removed "GtkSource.Region" from the "generate" list and we created a new entry for it. Then we say to gir that it should generate (through status = "generate"). However, we also tell it that we don't want the "is_empty" function to be generated.

Now that we've done that, we need to implement it. Let's create a src/region.rs file:

#![allow(unused)]
fn main() {
use glib::object::IsA;
use glib::translate::*;
use Region;

pub trait RegionExtManual: 'static {
    pub fn is_empty(&self) -> bool;
}

impl<O: IsA<Region>> RegionExtManual for O {
    pub fn is_empty(&self) -> bool {
        // blablabla
        true
    }
}
}

You might wonder: "why not just implementing it on the Region type directly?". Because like this, a subclass will also be able to use this trait easily as long as it implements IsA<Region>. For instance, in gtk, everything that implements IsA<Widget> (so almost every GTK types) can use those methods.

As usual, don't forget to reexport the trait. A little tip about reexporting manual traits: in gtk3-rs, we create a src/prelude.rs file which reexports all traits (both manual and generated ones), making it simpler for users to use them through use [DEPENDENCY]::prelude::*. The src/prelude.rs file looks like this:

#![allow(unused)]
fn main() {
pub use crate::auto::traits::*;
pub use region::RegionExtManual;
}

Then it's reexported as follows from the src/lib.rs file:

#![allow(unused)]
fn main() {
pub mod prelude;
pub use prelude::*;
}

Manually defined traits missing from the documentation

If you defined traits manually, you can add them to the "Implements" section in the documentation for classes and interfaces by using the manual_traits = [] option in the Gir.toml file. Here is an example:

[[object]]
name = "Gtk.Assistant"
status = "generate"
#add link to trait from current crate
manual_traits = ["AssistantExtManual"]

[[object]]
name = "Gtk.Application"
status = "generate"
#add link to trait from other crate
manual_traits = ["gio::ApplicationExtManual"]

Generating documentation

And finally the last feature. Just run the following command in the folder of the safe wrapper crate:

gir -c Gir.toml -d ../gir-files --doc-target-path docs.md -m doc
  • -d ../gir-files: flag to select the folder containing the .gir files.
  • --doc-target-path docs.md: flag to select the name of the markdown file containing the documentation.
  • -m doc: flag to select the work mode for generating the documentation.

It'll generate a markdown file if everything went fine. It contains all of the crate's documentation. If you want to put it into your crate's source code like "normal" doc comments, run:

cargo install rustdoc-stripper
rustdoc-stripper -g -o docs.md

And now your crate should be completely documented as expected!

Running the above commands again would duplicate the doc comments. Make sure to first remove the old ones before running the command again. You can do this by running the following commands:

rustdoc-stripper -s -n
rustdoc-stripper -g -o docs.md

Try building the documentation and also try it with the various features you might have:

cargo doc

For building the docs on systems without the required dependencies installed (for example docs.rs or CI jobs), you can use the docsrs attribute. This will disable the linking of the underlying C libraries and won't throw an error if those libraries are not available.

On top of that, the docsrs attribute will enable the doc_cfg feature which is only available in the nightly compiler toolchain at the time of writing. This is mostly useful for clarifying feature requirements through the docs.

To build the docs with the docsrs attribute, you can use the following command:

RUSTFLAGS='--cfg docsrs' RUSTDOCFLAGS='--cfg docsrs' cargo +nightly doc --all-features

Congratulations, we are done. You have successfully created the safe wrapper for a C library!

You can easily publish your generated bindings and the wrapper to crates.io to allow others to use it. Publishing crates is easy but keep in mind that they need to be maintained as well. We set up the project folder in a way that easily allows sharing the code. All that is needed is to add some information to your Cargo.toml. Gir will not override them when you re-generate bindings. Easy, right. If this is your first time publishing a crate, you can find a detailed guide here.

Before you publish the crate, please ensure docs.rs will activate the docsrs attribute. Feel free to go back to the chapter about the Cargo.toml file of the safe wrapper to read more about it. If you skip this step, your crate and all crates depending on it will not have documentation available on docs.rs.

Configuration

GIR uses two configurations files, one for generating the FFI part of the bindings and the other file for the Rust API. The configuration files must be named Gir.toml

  • The FFI configuration allows things such as ignoring objects, overriding the minimum required version for a specific type or renaming the generated crate name.

  • The Rust API configuration is a bit more complex as it allows configuring Objects, Enums, Bitfields, Functions, Properties, Signals and a few other things.

FFI Options

In FFI (-m sys) mode, gir generates as much as it can. So in this mode, the TOML file is mostly used to ignore some objects. To do so, you need to add its fullname to an ignore array. Example:

ignore = ["Gtk.Widget", "Gtk.Window"]

And that's all. Neither GtkWidget nor GtkWindow (alongside with their functions) will be generated.

You also need to add any needed external libraries in the "external_libraries" parameter. Example:

[options]
external_libraries = [
   "GLib",
   "GObject",
]

You can specify a few other options:

[options]
girs_directories = ["../gir-files"]
library = "GtkSource"
version = "3.0"
min_cfg_version = "3.0"
target_path = "."
# Path where lib.rs generated (defaults to <target_path>/src)
# auto_path = "src"
work_mode = "sys"
# If true then build.rs will be split into 2 parts:
# always generated build_version.rs,
# and build.rs that generated only if not exists.
# Defaults to false
split_build_rs = false
# Adds extra versions to features
extra_versions = [
   "3.15",
   "3.17",
]
# Change library version for version
[[lib_version_overrides]]
version = "3.16"
lib_version = "3.16.1"
# Add extra dependencies to feature
[[feature_dependencies]]
version = "3.16"
dependencies = [
  "glib-sys/v3_16"
]

Also, you can add rust cfg conditions on objects, functions and constants, for example, when flagging for conditional compilation:

[[object]]
name = "GstGL.GLDisplayEGL"
status = "generate"
cfg_condition = "feature = \"egl\""
    [[object.function]]
    pattern = ".*"
    cfg_condition = "feature = \"egl\""

Generation in FFI mode

When you're ready, let's generate the FFI part. In the command we'll execute, ../gir-files is where the directory with your .gir files is. (But again, you can just clone the gir-files repository and add your file(s) in it). Then let's run the command:

cargo run --release -- -c YourSysGirFile.toml -d ../gir-files -m sys -o the-output-directory-sys

The generated files will be placed in the-output-directory-sys. Just take care about the dependencies and the crate's name generated in the Cargo.toml file (update them if they don't work as expected).

You now have the sys part of your binding!

API Options

This mode requires you to write another TOML file. gtk/Gir.toml is a good example.

[options]
girs_directories = ["gir-files"]
library = "Gtk"
version = "3.0"
min_cfg_version = "3.4"
target_path = "."
# Path where objects generated (defaults to <target_path>/src/auto)
# auto_path = "src/auto"
work_mode = "normal"
# Whether the library uses https://gitlab.gnome.org/GNOME/gi-docgen for its documentation
use_gi_docgen = false
generate_safety_asserts = true
deprecate_by_min_version = true
# With this option enabled, versions for gir and gir-files saved only to one file to minimize noise,
# can also take path to the directory for saving "versions.txt" or filename with extension.
# Relative to target_path
single_version_file = true
# Trust the nullability information about return values. If this is disabled
# then any pointer return type is assumed to be nullable unless there is an
# explicit override for it.
# This has to be used carefully as many libraries are missing nullable
# annotations for return values, which then will cause a panic once an
# unexpected NULL is returned.
trust_return_value_nullability = false
# Disable running `cargo fmt` on generated files
# (defaults to false)
disable_format = true
# Always generate a Builder if possible. This is mostly a convenient setter as most of the
# time you might want the Builder to be generated. Ignoring none-desired ones can still be done with per object `generate_builder` configuration.
# (defaults to false)
generate_builder = true

This mode generates only the specified objects. You can either add the object's fullname to the generate array or add it to the manual array (but in this case, it won't be generated, just used in other functions/methods instead of generating an "ignored" argument). Example:

generate = ["Gtk.Widget", "Gtk.Window"]
manual = ["Gtk.Button"]

So in here, both GtkWidget and GtkWindow will be fully generated and functions/methods using GtkButton will be uncommented. To generate code for all global functions, add Gtk.* to the generate array.

To also generate a Builder struct for a widget, it needs to be set with the generate_builder flag in object configuration:

[[object]]
name = "Gtk.TreeView"
status = "generate"
generate_builder = true

If the object doesn't already have a Default implementation through a constructor method without arguments, generating a Builder struct will add a Default implementation for the object.

If you want to remove warning messages about the not bound Builders during the generation you don't want to be generated, you can ignore them with the generate_builder flag in object configuration:

[[object]]
name = "Gtk.TreeView"
status = "generate"
generate_builder = false

If there is some work which has to be done post-construction before the builder's build method returns, you can set the builder_postprocess value in the object configuration:

[[object]]
name = "Gtk.Application"
status = "generate"
generate_builder = true
builder_postprocess = "Application::register_startup_hook(&ret);"

For the duration of the code in builder_postprocess the binding ret will be the value to be returned from the build method.

Sometimes Gir understands the object definition incorrectly or the .gir file contains an incomplete or wrong definition, to fix it, you can use the full object configuration:

[[object]]
# object's fullname
name = "Gtk.SomeClass"
# can be also "manual" and "ignore" but it's simpler to just put the object in the same array
status = "generate"
# replace the parameter name for the child in child properties (instead "child")
child_name = "item"
# mark object as final type, i.e. one without any further subclasses. this
# will not generate trait SomeClassExt for this object, but implement all
# functions in impl SomeClass
final_type = true
# mark the object as a fundamental type in case the GIR file lacks the annotation
# note that fundamental types don't make use of IsA/Cast traits and you should
# implement something similar manually
# gir is only capable for generating the type definitions along with their functions
fundamental_type = false
# mark the enum as exhaustive. This must only be done if it is impossible for
# the C library to add new variants to the enum at a later time but allows
# for more optimal code to be generated.
exhaustive = false
# allow rename result file
module_name = "soome_class"
# override starting version
version = "3.12"
# prefixed object in mod.rs with #[cfg(mycond)]
cfg_condition = "mycond"
# if you want to generate builder with name SomeClassBuilder
generate_builder = true
# trust return value nullability annotations for this specific type.
# See above for details and use with care
trust_return_value_nullability = false
# Tweak the visibility of the type
visibility = "pub" # or 'crate' / 'private' / 'super'
# The default value to used for the `Default` implementation. It only
# works for flags and enums. You have to pass the "GIR" member name.
default_value = "fill"
# Change the name of the generated trait to e.g avoid naming conflicts
trait_name = "TraitnameExt" 
# In case you don't want to generate the documentation for this type.
generate_doc = false
    # define overrides for function
    [[object.function]]
    # filter functions from object
    name = "set_website_label"
    # alternative way to apply override for many functions. Will be used with '^' and '$' on both sides
    # can be used instead of `name` almost anywhere
    # pattern = "[gs]et_value"
    # don't generate function
    ignore = true
    # override starting version
    version = "3.12"
    # prefixed function with #[cfg(mycond)]
    cfg_condition = "mycond"
    # prefixed function with #[doc(hidden)]
    doc_hidden = true
    # define a list of function parameters to be ignored when the documentation is generated
    doc_ignore_parameters = ["some_user_data_param"]
    # disable length_of autodetection
    disable_length_detect = true
    # write function docs to trait other than default "xxxExt",
    # also works in [object.signal] and [object.property]
    doc_trait_name = "SocketListenerExtManual"
    # disable generation of future for async function
    no_future = true
    # to rename the generated function
    rename = "something_else"
    # to override the default safety assertions: "none", "skip",
    # "not-initialized", "in-main-thread"
    assertion = "in-main-thread"
    # Tweak the visibility of the function
    visibility = "pub" # or 'crate' / 'private' / 'super'
    # In case you don't want to generate the documentation for this method.
    generate_doc = false
        # override for parameter
        [[object.function.parameter]]
        # filter by name
        name = "website_label"
        # allow to remove/add Option<>
        nullable = true
        # Take the parameter by value instead of by ref
        move = true
        # allow to make parameter immutable
        const = true
        # parameter is calculated as length of string or array and removed from function declaration
        # (for length of return value use "return")
        length_of = "str"
        # change string type. Variants: "utf8", "filename", "os_string"
        string_type = "os_string"
        # make function unsafe to call (emits `fn unsafe`)
        unsafe = true

        # override for return value
        [object.function.return]
        # allow to remove/add Option<> to return value
        nullable = true
        # convert bool return types to Result<(), glib::BoolError> with
        # the given error message on failure
        bool_return_is_error = "Function failed doing what it is supposed to do"
        # convert Option return types to Result<T, glib::BoolError> with
        # the given error message on failure
        nullable_return_is_error = "Function failed doing what it is supposed to do"
        # always include the return value of throwing functions in the returned Result<...>,
        # without this option bool and guint return values are assumed to indicate success or error,
        # and are not included in the returned Result<...>
        use_return_for_result = true
        # change string type. Variants: "utf8", "filename", "os_string"
        string_type = "os_string"
        # overwrite type
        type = "Gtk.Widget"

            # Override callback's parameter
            [[object.function.parameter.callback_parameter]]
            name = "name_of_the_callback_parameter"
            nullable = true
    # virtual methods support the same configuration for parameters and return types as functions
    # note that they are not used for code generation yet.
    [[object.virtual_method]]
    # filter virtual method from object
    name = "set_website_label"
    # alternative way to apply override for many functions. Will be used with '^' and '$' on both sides
    # can be used instead of `name` almost anywhere
    # pattern = "[gs]et_value"
    # don't generate function
    ignore = true
    # override starting version
    version = "3.12"
    # prefixed function with #[cfg(mycond)]
    cfg_condition = "mycond"
    # prefixed function with #[doc(hidden)]
    doc_hidden = true
    # define a list of function parameters to be ignored when the documentation is generated
    doc_ignore_parameters = ["some_user_data_param"]
    # write function docs to trait other than default "xxxExt",
    # also works in [object.signal] and [object.property]
    doc_trait_name = "SocketListenerExtManual"
    # to rename the generated function
    rename = "something_else"
    # In case you don't want to generate the documentation for this method.
    generate_doc = false
    [[object.signal]]
    name = "activate-link"
    # replace trampoline bool return type with `Inhibit`
    inhibit = true
    ignore = true
    version = "3.10"
    doc_hidden = true
    # In case you don't want to generate the documentation for this signal.
    generate_doc = false
        [[object.signal.parameter]]
        name = "path_string"
        # allow to use different names in closure
        new_name = "path"
        # can be also "borrow" and "none": Add some transformation between ffi trampoline parameters and rust closure
        transformation = "treepath"
        nullable = true
        [object.signal.return]
        nullable = true
    # override for properties
    [[object.property]]
    name = "baseline-position"
    version = "3.10"
    ignore = true
    # In case you don't want to generate the documentation for this property.
    generate_doc = false
    [[object.property]]
    name = "events"
    # generate only `connect_property_events_notify`, without `get_property_events` and `set_property_events`
    # supported values: "get", "set", "notify"
    generate = ["notify"]

Since there are no child properties in .gir files, it needs to be added for classes manually:

[[object]]
name = "Gtk.SomeClassWithChildProperties"
status = "generate"
# replace parameter name for child in child properties (instead of "child")
child_name = "item"
# define concrete child type (instead of "Widget")
child_type = "Gtk.MenuItem"
    [[object.child_prop]]
    name = "position"
    type = "gint"
    doc_hidden = true

For enumerations and bitflags, you can configure the members and mark the type as #[must_use]:

[[object]]
name = "Gdk.EventType"
status = "generate"
# generates #[must_use] attribute for the type
must_use = true
# override starting version
version = "3.12"
    [[object.member]]
    name = "2button_press"
    # allows to skip elements with bad names, other members with same value used instead
    alias = true
    # Allow to add a cfg condition
    cfg_condition = "target_os = \"linux\""
    # In case you don't want to generate the documentation for this member.
    generate_doc = false
    [[object.member]]
    name = "touchpad_pinch"
    # define starting version when member added
    version = "3.18"

For enumerations and bitflags, you can also configure additional #[derive()] clauses optionally conditioned to a cfg.

[[object]]
name = "Gst.Format"
status = "generate"
    [[object.derive]]
    name = "Serialize, Deserialize"
    cfg_condition = "feature = \"serde\""

For global functions, the members can be configured by configuring the Gtk.* object:

[[object]]
name = "Gtk.*"
status = "generate"
    [[object.function]]
    name = "stock_list_ids"
    # allows to ignore global functions
    ignore = true
    # allows to define if the function was moved to a trait 
    doc_trait_name = "StockExt"
    # allows to define if the function was moved to a struct
    doc_struct_name = "Stock"
    # In case you don't want to generate the documentation for this function.
    generate_doc = false

Which will prevent gir from generating stock_list_ids. If you want to specify that a function will be manually implemented, you can use:

[[object]]
name = "Gtk.Entry"
status = "generate"
    [[object.function]]
    name = "get_invisible_char"
    manual = true

This will prevent gir from generating get_invisible_char and it won't generate get_property_invisible_char which would have been generated if we had used "ignore = true".

Note that you must not place Gtk.* into the generate array and additionally configure its members.

You can control the generation of constants in a similar fashion:

[[object]]
name = "Gtk.*"
status = "generate"
    [[object.constant]]
    pattern = "*"
    # No constants will be generated
    ignore = true
    # In case you don't want to generate the documentation for this constant.
    generate_doc = false

Constants also support version and cfg_condition fields.

In various cases, GObjects or boxed types can be used from multiple threads and have certain concurrency guarantees. This can be configured with the concurrency setting at the top-level options or per object. It will automatically implement the Send and Sync traits for the resulting object and set appropriate trait bounds for signal callbacks. The default is none, and apart from that send and send+sync are supported.

[[object]]
# object's fullname
name = "Gtk.SomeClass"
# can be also "manual" and "ignore" but it's simpler to just put the object in the same array
status = "generate"
# concurrency of the object, default is set in the top-level options or
# otherwise "none". Valid values are "none", "send" and "send+sync"
concurrency = "send+sync"

Note that send is only valid for types that are either not reference counted (i.e. clone() copies the object) or that are read-only (i.e. no API for mutating the object exists). send+sync is valid if the type can be sent to different threads and all API allows simultaneous calls from different threads due to internal locking via e.g. a mutex.

[[object]]
name = "Gtk.Something"
status = "manual"
# Can also be "ref-mut", "ref-immut"
ref_mode = "ref"

When manually generating bindings, it can happen that the reference mode detected by GIR is different than what was implemented and conversion to the C types are wrong in autogenerated functions that have such objects as argument. This can be overridden with the ref_mode configuration.

Getters are automatically renamed to comply with Rust codying style guidelines. However, this can cause name clashes with existing functions. If you want to bypass the automatic renaming mechanism, use bypass_auto_rename = true:

[[object]]
name = "Gtk.TextBuffer"
[...]
    [[object.function]]
    name = "get_insert"
    # Avoid clash with the `insert` operation.
    bypass_auto_rename = true

Some constructors are not annotated as constructor in the gir files. In order for the naming convention to be applied, you can force a function to be considered as a constructor:

[[object.function]]
name = "new_for_path"
# Not annotated as constructor in Gir => force it to apply naming convention
constructor = true

conversion_type "Option"

The conversion_type variant Option is available for types T implementing glib::TryFromGlib<Error=GlibNoneError>. As a reminder, this allows implementing FromGlib for Option<T> and usually goes alongside with ToGlib for both T and Option<T>. In this case, Option<T> will be used for return values (including ffi output arguments). For in-arguments, except if the parameter is declared mandatory, impl Into<Option<T>> so that either an Option<T> or T can be used.

Ex. from gstreamer-rs:

#![allow(unused)]
fn main() {
[[object]]
name = "Gst.ClockTime"
status = "manual"
conversion_type = "Option"
}

The type ClockTime implements glib::TryFromGlib<Error=GlibNoneError> (and OptionToGlib), which means that its Rust representation can take advantage of Option<ClockTime>.

Additionally, the user can instruct gir to expect Some or Ok results for specific arguments or return values. E.g.:

#![allow(unused)]
fn main() {
[[object]]
name = "Gst.Clock"
status = "generate"
manual_traits = ["ClockExtManual"]
    [[object.function]]
    name = "get_calibration"
        [[object.function.parameter]]
        name = "internal"
        mandatory = true
}

In the above example, the user instructs gir to consider the internal argument (which also happens to be an out argument) with type gir Gst.ClockTime can be represented as a ClockTime without the Option. This argument is actually part of a set of output arguments. With the above gir declaration, the generated signature is the following (the implementation takes care of expecting the value to be defined):

#![allow(unused)]
fn main() {
    fn get_calibration(
        &self,
    ) -> (
        ClockTime,
        Option<ClockTime>,
        Option<ClockTime>,
        Option<ClockTime>,
    );
}

For a return value, the mandatory declaration reads:

#![allow(unused)]
fn main() {
    [[object.function]]
    name = "util_get_timestamp"
    /.../
        [object.function.return]
        always returns a value
        mandatory = true
}

conversion_type "Result"

The conversion_type variant Result is available for types T implementing glib::TryFromGlib<Error=Err> where Err is neither GlibNoneError nor GlibNoneOrInvalidError. In this case, Result<T, ErrorType> will be used for return values (including ffi output arguments) and the type itself in argument position.

In gstreamer-rs, the C type GstStateChangeReturn can represent both a successful or an error return value. In Rust, the Result enum is the idiomatic way of returning an error. In gstreamer-rs, bindings to functions returning GstStateChangeReturn had to be manually implemented so as to return Result<StateChangeSuccess, StateChangeError>. Note that in this case, the type implementing TryFromGlib is StateChangeSuccess and not GstStateChangeReturn. These functions can be auto-generated using:

#![allow(unused)]
fn main() {
[[object]]
name = "Gst.StateChangeReturn"
status = "generate"
must_use = true
    [object.conversion_type]
    variant = "Result"
    ok_type = "gst::StateChangeSuccess"
    err_type = "gst::StateChangeError"
}

Boxed types vs. BoxedInline types

For boxed types / records, gir auto-detects copy/free or ref/unref function pairs for memory management on records. It falls back to generic g_boxed_copy/g_boxed_free if these are not found, based on an existing implementation of get_type. Otherwise no record implementation can be generated.

This works for the majority of boxed types, which are literally boxed: their memory is always allocated on the heap and memory management is left to the C library. Some boxed types, however, are special and in C code they are usually allocated on the stack or inline inside another struct. As such, their struct definition is public and part of the API/ABI. Usually these types are relatively small and allocated regularly, which would make heap allocations costly.

These special boxed types are usually allocated by the caller of the C functions, and the functions are then only filling in the memory and taking values of this type as (out caller-allocates) parameters.

In most other bindings, heap allocations are used for these boxed types too but in Rust we can do better and actually allocate them on the stack or inline inside another struct.

Gir calls these special boxed types "boxed inline".

[[object]]
name = "GLib.TreeIter"
status = "generate"
boxed_inline = true

For inline-allocated boxed types it is possible to provide Rust expressions in the configuration for initializing newly allocated memory for them, to copy from one value into another one, and to free any resources that might be stored in values of that boxed types.

By default the memory is zero-initialized and copying is done via std::ptr::copy(). If the boxed type contains memory that needs to be freed then these functions must be provided.

The following configuration is equivalent with the one above.

[[object]]
name = "GLib.TreeIter"
status = "generate"
boxed_inline = true
init_function_expression = "|_ptr| ()"
copy_into_function_expression = "|dest, src| { std::ptr::copy_nonoverlapping(src, dest, 1); }"
clear_function_expression = "|_ptr| ()"

Generation in API mode

To generate the Rust-user API level, The command is very similar to the previous one. It's better to not put this output in the same directory as where the FFI files are. Just run:

cargo run --release -- -c YourGirFile.toml -d ../gir-files -o the-output-directory

Now it should be done. Just go to the output directory (so the-output-directory/auto in our case) and try to build using cargo build. Don't forget to update your dependencies in both projects: nothing much to do in the FFI/sys one but the Rust-user API level will need to have a dependency over the FFI/sys one.

Now, at your crate entry point (generally lib.rs), add the following to include all generated files:

#![allow(unused)]
fn main() {
pub use auto::*;
}

Add manual bindings alongside generated code

Unfortunately, gir isn't perfect (yet) and will certainly not be able to generate all the code on its own. So here's what a gir generated folder looks like:

- your_folder
|
|- Cargo.toml
|- src
 |
 |- lib.rs
 |- auto
  |
  |- (all files generated by gir)

You can add your manual bindings directly inside the src folder (at the same level as lib.rs). Then don't forget to reexport them. Let's say you added a Color type in a color.rs file. You need to add in lib.rs:

#![allow(unused)]
fn main() {
// We make the type public for the API users.
pub use color::Color;

mod color;
}

Crate name overrides

gir uses simple rule to convert a namespace to a crate name and it sometimes goes wrong. For example, "WebKit2WebExtension" namespace will be converted to "web_kit2_web_extension", which looks bad.

To fix it, the crate_name_overrides option can be used.

It also replaces FFI crates' name.

[crate_name_overrides]
"web_kit2_web_extension" = "webkit2_webextension"