Integrating a Rust library into Gecko

Table of contents

Integrating a Rust library to Gecko / Firefox is unexpectedly easy, but Rust adoption varies significantly between teams. I started in the Firefox networking Team Necko, which has a few Rust components like the HTTP/3 stack neqo or the URL implementation. After switching to Privacy / Anti-Tracking team I'm now in a team that has no Rust code at all. The interest for Rust components is there, but usually projects are started in C++ due to all code being in C++.

Rust in Gecko

Pie chart with language usage

Looking at current language stats, we can see that Rust is currently at around 12% of Firefox code with almost 5 million lines of code. This is not neglectable, but falls short behind C / C++ and also JavaScript.

Error categories rules out by Rust

Other Rusty reasons

In my opinion, Rust is just a very well-designed language. It has great documentation infrastructure and awesome compiler errors aiding you in writing safe code.

The patch for the case study can be found in D225390.

Almost always you probably want to split up the library into two parts:

One crate for functionality and bindings. The functionality crate can be developed out-of-tree. In this example I'm going to have the crate be developed in-tree.

Functionality crate

The crate implementing the logic with no dependency on xpcom types simply needs to have a single-line (plus license text) moz.build next to the Cargo.toml:

FINAL_LIBRARY = "xul"

cbindgen / XPCOM bindings crate

You need a few more lines for the bindings crate to autogenerate a C++ header with cbindgen.

FINAL_LIBRARY = "xul"
EXPORTS.mozilla += [
   "UrlStrip.h",
]
if CONFIG["COMPILE_ENVIRONMENT"]:
   CbindgenHeader(
       "UrlStrip_ffi.h",
       inputs=["/toolkit/components/antitracking/urlstrip_glue"]
   )
   EXPORTS.mozilla += [
       "!UrlStrip_ffi.h",
   ]

You can depend on nsstring and other xpcom crates. However, you won't be able to use Rust's native #[test] directive. This is one of the reasons you likely really want to have the separation between the two crate types.

To configure cbindgen, get inspired by taking a look at other cbindgen.toml files.

You also want to have somewhat nicer C++ headers around your autogenerated cbindgen header for C++ UrlStrip.h:

struct UrlStrip {
public:
 static void Create(UrlStrip** pThis) { urlstrip_new(pThis); }
 void Delete() { urlstrip_drop(this); }

 void Init() { urlstrip_init(this); };
 void Uninit() { urlstrip_uninit(this); };

 nsresult Strip(const nsACString& aURI, nsCString* aOutURI) {
   return urlstrip_strip(this, &aURI, aOutURI);
 }
 bool CanStrip(const nsACString& aURI) {
   return urlstrip_canstrip(this, &aURI);
 }

private:
 UrlStrip() = delete;
 ~UrlStrip() = delete;
 UrlStrip(const UrlStrip&) = delete;
 UrlStrip& operator=(const UrlStrip&) = delete;
};

Maybe cxx could ease with this in the future: Bug 1921139 - Allow creating C++ bindings for Rust code with cxx.

Other files to touch

To use the Rust library from C++/JS, it must be linked. And for that you need to both add it as a dependency to toolkit/library/rust/shared/Cargo.toml:

urlstrip = { path = "../../../components/antitracking/urlstrip" }
urlstrip_glue = { path = "../../../components/antitracking/urlstrip_glue" }

And also important (due to still using 2015 rust edition) extern crate definitions in toolkit/library/rust/shared/lib.rs:

extern crate urlstrip;
extern crate urlstrip_glue;

Done

This is all. Feel free to reach out to me if you have any questions!

Further Resources

Tags: #gecko #rust #mozilla #firefox