Commit 28fa7dbf authored by Federico Mena Quintero's avatar Federico Mena Quintero
Browse files this project is dead - describe what went wrong

parent cac754bb
Pipeline #365769 failed with stages
in 3 minutes and 29 seconds
As of 2022, if you want to write GObject classes in Rust, please use
the [subclass mechanism in glib-rs][subclass] instead!
This project is no longer paying attention to issues or merge
requests; it is just here for posterity.
## A post-mortem of gnome-class, more or less
Gnome-class started during the first [Rust/GNOME hackfest][hackfest]
in Mexico City in 2017. At that time, an early version of gtk-rs
already existed, but it was just a C->Rust binding. It did not
contemplate implementing GObject subclasses in Rust.
At that time, a convenient way to write GObject-derived code in a
language that looked vaguely like C, but at a higher level, was by
using [Vala][Vala]:
class Demo.HelloWorld : GLib.Object {
public static int main(string[] args) {
stdout.printf("Hello, World\n");
return 0;
class Person : GLib.Object {
private int _age = 32;
/* Property */
public int age {
get { return _age; }
set { _age = value; }
This syntax is very comfortable/familar to people coming from a C# or
Java background. So the reasoning was something like this:
* Rust can call C functions just fine. One defines new GObjects by
calling C functions.
* A mythical "compiler plugin" could take the Vala-like syntax above,
and turn it into the boilerplate to register GObject classes, their
properties, and signals.
* The actual code inside method implementations would be plain Rust,
and the "compiler plugin" would leave them untouched. Maybe it
would just write appropriate function signatures, and have some
magic trampolines to convert C and GObject types into Rust types.
This became `gnome-class`, a Rust procedural macro with more or less
the following syntax for the `gobject_gen!` macro:
gobject_gen! {
class MyClass: GObject {
foo: Cell<i32>,
bar: RefCell<String>,
impl MyClass {
virtual fn my_virtual_method(&self, x: i32) {
... do something with x ...
The idea was that `class MyClass` would be turned into a struct with
the appropriate fields for deriving from `GObject`, and with private
data to hold the `foo` and `bar` fields. The proc macro would create
a `GObjectClass`-derived struct with a vtable slot for
`my_virtual_method`, and create an appropriate `#[no_mangle] pub fn
my_class_my_virtual_method(zelf: , x: i32)` callable from C code.
### Growing pains
In 2017 and 2018, Rust's procedural macros were still in heavy
development, as well as the surrounding infrastructure like the `syn`
and `quote` crates. In particular, `syn` often made incompatible API
changes as it was developed.
I (Federico) was not working full-time on gnome-class, so those kinds
of changes gave me a lot of trouble — every time `syn` changed
its API, *all* of gnome-class would break and I would have no idea of
how to fix it.
Also, I'm not a compilers person. So, I was trying to learn how to
write a compiler, how to use early iterations of proc-macros, and how
to do GObject in Rust all at the same time. I was just overwhelmed.
### Incorrect assumptions in the architecture of gnome-class
Once we had code generation more or less working, it became desirable
to also output C header files for the generated API and GObject
Introspection `.gir` data. You can see the latter in
[`src/gen/`](src/gen/ However, doing this from the
procedural macro, i.e. *when the Rust code is compiled* is hugely
It seems super-fishy to allow the Rust compiler to write to arbitrary
files just determined by the proc-macro. Also, it is hard for the
macro to know the environment's conventions about where to place
generated files.
You know what does this sort of thing much better?
[Wasm-bindgen][wasm-bindgen]. It also wants to generate some magic
wrappers around Rust code, and then emit some metadata for them —
analogous to the GObject boilerplate that the proc-macro generates in
gnome-class, plus the GIR information.
Apart from the trampolines and other boilerplate, Wasm-bindgen
essentially emits its metadata by creating specially-named functions
like this:
pub extern "C" fn _specially_named_function_that_outputs_metadata() -> Metadata {
Later, the `wasm-bindgen` tool opens the binaries, calls these
functions to extract the metadata, and generates the wrapper
Javascript code and anything else that may be needed.
That is, it puts the "generate non-Rust code" part in a totally
separate binary from the Rust compiler, and leaves it under the user's
control. This is much friendlier to build systems, and is a better
architecture than burdening rustc with random tasks.
There are some [notes on how wasm-bindgen
works](gobject-notes/notes.txt), but they are probably very outdaded —
by this time you should better check wasm-bindgen's actual
documentation or code.
### Problems that I don't quite remember if we solved
***Types:*** Not all of gobject's fundamental types have a 1:1 mapping to Rust
primitive types, but things like GObject signals really need accurate
function prototypes. It also needs proper translation of incompatible
types that look like they should be compatible. If you write this in
the `gobject_gen` syntax:
impl MyObject {
signal fn a_signal_that_carries_a_string(&self, s: &str);
Should the signal trampoline create a `CString` and pass its pointer
to the other side? Or should you be constrained to passing a special
`CString`-like object from the Rust code?
***C parent classes, Rust derived classes:*** If you have a C struct like this with bitfields:
typedef struct {
GObject parent;
int my_field;
Then gtk-rs's `gir` needs to be able to reconstruct an equivalent
`#[repr(C)]` struct so that the Rust compiler will know the size of
the struct on the C side. We had some problems with C structs with
bitfields, as `gir` couldn't handle them. I don't know if these were
removed/fixed in GTK4 or if `gir` was made to support them.
# Postscript: the initial
What follows is the old contents of ``. Maybe you can get
something useful out of it, but it's mostly outdated now.
# Gnome-class: implement GObjects in Rust with no boilerplate
[GObject][gobject] is the C-based object system for [GTK+][gtk] and
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment