Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Federico Mena Quintero
gnome-class
Commits
28fa7dbf
Commit
28fa7dbf
authored
Feb 14, 2022
by
Federico Mena Quintero
Browse files
README.md: this project is dead - describe what went wrong
parent
cac754bb
Pipeline
#365769
failed with stages
in 3 minutes and 29 seconds
Changes
1
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
README.md
View file @
28fa7dbf
# THIS PROJECT IS NO LONGER ACTIVE!
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.
[
subclass
]:
https://gtk-rs.org/gtk4-rs/stable/latest/book/gobject_subclassing.html
## 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
]
:
```
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:
```
rust
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/gir.rs`
](
src/gen/gir.rs
)
. However, doing this from the
procedural macro, i.e.
*when the Rust code is compiled*
is hugely
problematic.
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:
```
rust
#[no_mangle]
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.
[
hackfest
]:
https://wiki.gnome.org/Hackfests/Rust2017
[
Vala
]:
https://wiki.gnome.org/Projects/Vala
[
wasm-bindgen
]:
https://github.com/rustwasm/wasm-bindgen
### 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:
```
c
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 README.md
What follows is the old contents of
`README.md`
. 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
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment