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
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
- Use after free bugs: No more
kungFuDeathGrip
security fixes to extend the lifetime of an object. - Data races: No more forgetting to question "Do I need to lock a Mutex to access this variable?"
- Nullptr dereferences: Call by pointer is very common in C++, especially in idl interfaces. "Can this pointer I’ve got passed be null?" is now answered by the Rust type system.
- Iterator invalidation: Another big class of errors. In Gecko this is also memory safe for nsTArray, but wrong code.
- Other undefined behavior
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.
Case Study: RIIR "Copy Clean Link"
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!