The other day I wrote about how most of librsvg's library code is in Rust now.
Today I finished porting the GObject boilerplate for the main
RsvgHandle
object into Rust. This means that the C code no longer
calls things like g_type_register_static()
, nor implements
rsvg_handle_class_init()
and such; all those are in Rust now. How
is this done?
The life-changing magic of glib::subclass
Sebastian Dröge has been working for many months on refining utilities to make it possible to subclass GObjects in Rust, with little or no unsafe code. This subclass module is now part of glib-rs, the Rust bindings to GLib.
Librsvg now uses the subclassing functionality in glib-rs, which takes care of some things automatically:
- Registering your GObject types at runtime.
- Creating safe traits on which you can implement
class_init
,instance_init
,set_property
,get_property
, and all the usual GObject paraphernalia.
Check this out:
use glib::subclass::prelude::*;
impl ObjectSubclass for Handle {
const NAME: &'static str = "RsvgHandle";
type ParentType = glib::Object;
type Instance = RsvgHandle;
type Class = RsvgHandleClass;
glib_object_subclass!();
fn class_init(klass: &mut RsvgHandleClass) {
klass.install_properties(&PROPERTIES);
}
fn new() -> Self {
Handle::new()
}
}
In the impl
line, Handle
is librsvg's internals object — what used
to be RsvgHandlePrivate
in the C code.
The following lines say this:
-
const NAME: &'static str = "RsvgHandle";
- the name of the type, for GType's perusal. -
type ParentType = glib::Object;
- Parent class. -
type Instance
,type Class
- Structs with#[repr(C)]
, equivalent to GObject's class and instance structs. -
glib_object_subclass!();
- All the boilerplate happens here automatically. -
fn class_init
- Should be familiar to anyone who implements GObjects!
And then, a couple of the property declarations:
static PROPERTIES: [subclass::Property; 11] = [
subclass::Property("flags", |name| {
ParamSpec::flags(
name,
"Flags",
"Loading flags",
HandleFlags::static_type(),
0,
ParamFlags::READWRITE | ParamFlags::CONSTRUCT_ONLY,
)
}),
subclass::Property("dpi-x", |name| {
ParamSpec::double(
name,
"Horizontal DPI",
"Horizontal resolution in dots per inch",
0.0,
f64::MAX,
0.0,
ParamFlags::READWRITE | ParamFlags::CONSTRUCT,
)
}),
// ... etcetera
];
This is quite similar to the way C code usually registers properties for new GObject subclasses.
The moment at which a new GObject subclass gets registered against the
GType system is in the foo_get_type()
call. This is the C code in
librsvg for that:
extern GType rsvg_handle_rust_get_type (void);
GType
rsvg_handle_get_type (void)
{
return rsvg_handle_rust_get_type ();
}
And the Rust function that actually implements this:
#[no_mangle]
pub unsafe extern "C" fn rsvg_handle_rust_get_type() -> glib_sys::GType {
Handle::get_type().to_glib()
}
Here, Handle::get_type()
gets implemented automatically by
Sebastian's subclass traits. It gets things like the type name and
the parent class from the impl ObjectSubclass for Handle
we saw
above, and calls g_type_register_static()
internally.
I can confirm now that implementing GObjects in Rust in this way, and exposing them to C, really works and is actually quite pleasant to do. You can look at librsvg's Rust code for GObject here.
Further work
There is some auto-generated C code to register librsvg's error enum and a flags type against GType; I'll move those to Rust over the next few days.
Then, I think I'll try to actually remove all of the library's entry points from the C code and implement them in Rust. Right now each C function is really just a single call to a Rust function, so this should be trivial-ish to do.
I'm waiting for a glib-rs release, the first one that will have the
glib::subclass
code in it, before merging all of the above into
librsvg's master branch.
A new Rust API for librsvg?
Finally, this got me thinking about what to do about the Rust bindings
to librsvg itself. The rsvg crate uses the gtk-rs
machinery to generate the binding: it reads the GObject
Introspection data from Rsvg.gir
and generates a Rust binding
for it.
However, the resulting API is mostly identical to the C API. There is
an rsvg::Handle
with the same methods as the ones from C's
RsvgHandle
... and that API is not particularly Rusty.
At some point I had an unfinished branch to merge rsvg-rs into
librsvg. The intention was that librsvg's build procedure
would first build librsvg.so
itself, then generate Rsvg.gir
as
usual, and then generate rsvg-rs from that. But I got tired of
fucking with Autotools, and didn't finish integrating the projects.
Rsvg-rs is an okay Rust API for using librsvg. It still works
perfectly well from the standalone crate. However, now
that all the functionality of librsvg is in Rust, I would like to take
this opportunity to experiment with a better API for loading and
rendering SVGs from Rust. This may make it more clear how to refactor
the toplevel of the library. Maybe the librsvg
project can provide
its own Rust crate for public consumption, in addition to the usual
librsvg.so
and Rsvg.gir
which need to remain with a stable API and
ABI.