Much of the initial work on rustifying GJS has been investigation, reading, planning. All in order to get the bindings to various libraries organised. I can't do much without bindings so this is pretty much critical. The way Rust is able to use/link to C/C++ libraries is via FFI.
Rust FFI - Foreign Function Interface
Rust is designed to be interoperable with C interfaces. Currently it is not able to call C++ libraries directly.
Using FFI in Rust is a fairly simple affair, and functions rather similar to C; you declare in your Rust code with an
extern "C" block the function signatures you want to use from the C code/library, and the same for C code calling Rust (which must be compiled as either a
staticlib or a
The caveat with this however is that calls from Rust to C code must be wrapped in
unsafe blocks as the Rut compiler is unable to verify the safety of external calls - it is up to the devloper to guarantee the safety of such. This is particularly true of anything using a pointer.
More information can be found in the official documentation.
The libraries I need to interact with using Rust are;
As of now there are three options for generation of bindings for GNOME libraries:
gir. These last two rely on the availability or ability to generate a Gnome IR.
bindgen is a tool developed by the Mozilla Servo team, its summary is "Automatically generates Rust FFI bindings to C and C++ libraries". It does this, and does it surprisingly well, in-fact it produced a near carbon copy of the bindings produced by
grust-gen; just a lot less pretty. I also had issues with using bindgen in an experiment on generating GJS bindings - it dives very deep in to source code and follows headers pretty much everywhere; this also poses issues with Linux distribution packaging.
The mozjs library (that is, the SpiderMonkey JS engine) is used in servo - Mozilla's next-gen browser - and since servo is largely written in Rust, it also needs bindings to the mozjs library. Which is where bindgen comes in to play. Work has already been done to use bindgen to generate the bindings required for mozjs but there was a small problem for me; I need mozjs-52, but the current work is based on mozjs HEAD and will continue to be so for a while.
No problem. I just forked and downgraded mozjs to the required version. This all does raise a few questions however, in regards to how Linux distributions and packaging are going to handle it. Servo isn't ready, and won't be for a long while yet so it may not be an issue at all, but some points to consider and what impact they have on rusting GJS are;
- I will need to maintain a fork of mozjs at version 52
- GJS relies on stable releases of mozjs, the upcoming stable release is v52
- If my work produces meaningful and successful results then if it becomes mainstream;
- It introduces a build chain dependency on some rust tooling, and rust crates related to the binding
- Many distributions currently lack tooling to package rust written software and/or their dependencies (crates)
"mozjs is written in C++ though", I hear you cry. Yes... Fortunately for me the binding 'C' glue has been written via the Servo project by some very ace people.
I will touch on some of these issues at a later date. Currently some fedora and Red Hat employees are working on a section of these issues.
grust-gen operates similar to bindgen but uses GIR files for input, rather than spelunking in source code. There are multiple parts to this project, to Rust, from Rust to GIR, and what looks like an introspection library. However, this project is old and unmaintained.
gir is the new kid on the block and is part of the gtk-rs project. As with grust-gen, it uses GIR files to generate the bindings. It is also very active, and produces some rather nice code since it is built oriented towards GIR specifically.
The default mode for this tool is to generate all possible bindings from the specified gir file, and can also use an ignore list to bloke generating of things you may not need or aren't stable. The other end of this is to specify only the gir objects you want to have generated. The tool also provides facilities to generate bindings using a custom object configuration (this feature may come in handy later).
One more feature of gir is the ability to label certain GObjects as
sync, or both. What this means in Rust terms is that they gain an extra trait which marks them as thread safe. With regards to JS, this may likely become a valuable feature.
What about GJS?
gir cover the mozjs and GNOME libraries, but what about GJS? For any Rust I write that needs to call functions within GJS, I'll need to write my own bindings piecemeal as needed. Generating a GIR to use with
gir is likely to introduce a cycle since to build the GJS library, the Rust code needs to be compiled, and to compile the Rust code, the GJS bindings are needed. So easiest and only option is to write the bindings as needed. Not too hard.
Not issues relating specifically to Rust (I will talk about those another time), but to do with mozjs. As most of you are aware (most) Linux distributions package only stable software and refrain from the use of git. For me this poses some challenges regarding GJS - because I had to fork the Mozilla servo provided fork of mozjs to downgrade it to the version required, I now have the burden of maintaining it. Maybe that won't be an issue in future, but for now it means it is unlikely that any work I do on GJS using this fork will end up in the main trunk of GJS. If I had used the HEAD of the servo mozjs, that would definitely be ruled out.
Now I am looking at 3 things;
- full conversion using the servo/mozjs
- convert only functions which use no mozjs functions or structures, or
- use what I learn from this process, and my knowledge of how Rust works to provide safety, to help make GJS a bit safer (for example, use of move semantics and unique_ptr can offer comparable features)
I'm unclear of what possible benefits converting the very slim amount of non-mozjs functions may give, as I had initially envisioned converting a function that creates/destroys memory, or is a hot function so that I could use some form of metrics. I'm going to continue on this path of course, as it could still provide useful insights or some benefits I haven't thought of.
I will also continue with the full conversion as this is the only way to do meaningful comparisons and metrics - it is also entirely possible for it to end up being viable too.
And I will use what I learn to help improve the C/C++ code of GJS in the interim.
My next post will hopefully have some insights and comparison between C++ and Rust, likely more to do with language structure, semantics, and a comparison of safety. Metrics would be nice, but that requires something to measure - we'll see how that bit goes.