fune/docs/writing-rust-code/xpcom.md
Nicholas Nethercote 977d5b8a57 Bug 1648348 - Create "Writing Rust Code" docs. r=froydnj,zbraniecki,lina.
This patch includes content from the following places.

- Lina's "Getting Rusty: How to ship an XPCOM component in Firefox" slide deck.
- Zibi's "Rust <--> C/C++ FFI for newbies" gist.

It also links to Emilio's "FFI patterns #1 - Complex Rust data structures
exposed seamlessly to C++" blog post. I was going to include that content, but
it's very long, so I have omitted it for now.

Differential Revision: https://phabricator.services.mozilla.com/D81963
2020-07-07 07:38:27 +00:00

123 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# XPCOM components in Rust
XPCOM components can be written in Rust.
## A tiny example
The following example shows a new type that implements `nsIObserver`.
First, create a new empty crate (e.g. with `cargo init --lib`), and add the
following dependencies in its `Cargo.toml` file.
```toml
[dependencies]
libc = "0.2"
nserror = { path = "../../../xpcom/rust/nserror" }
nsstring = { path = "../../../xpcom/rust/nsstring" }
xpcom = { path = "../../../xpcom/rust/xpcom" }
```
(The number of `../` occurrences will depend on the depth of the crate in the
file hierarchy.)
Next hook it into the build system according to the [build
documentation](../build/buildsystem/rust.html).
The Rust code will need to import some basic types. `xpcom::interfaces`
contains all the usual `nsI` interfaces.
```rust
use libc::c_char;
use nserror::nsresult;
use std::sync::atomic::{AtomicBool, Ordering};
use xpcom::{interfaces::nsISupports, RefPtr};
```
The next part declares the implementation.
```rust
#[derive(xpcom)]
#[xpimplements(nsIObserver)]
#[refcnt = "atomic"]
struct InitMyObserver {
ran: AtomicBool,
}
```
It defines an initializer struct, prefixed with `Init`, with three attributes.
- Some `derive` magic.
- An `xpimplements` declaration naming the interface(s) being implemented.
- The reference count type.
Next, all interface methods are declared in the `impl` block as `unsafe` methods.
```rust
impl MyObserver {
#[allow(non_snake_case)]
unsafe fn Observe(
&self,
_subject: *const nsISupports,
_topic: *const c_char,
_data: *const i16,
) -> nsresult {
self.ran.store(true, Ordering::SeqCst);
nserror::NS_OK
}
}
```
These methods always take `&self`, not `&mut self`, so we need to use interior
mutability: `AtomicBool`, `RefCell`, `Cell`, etc.
XPCOM methods are unsafe by default, but the
[xpcom_method!](https://searchfox.org/mozilla-central/source/xpcom/rust/xpcom/src/method.rs)
macro can be used to clean this up. It also takes care of null-checking and
hiding pointers behind references, lets you return a `Result` instead of an
`nsresult,` and so on.
To use this type within Rust code, do something like the following.
```rust
let observer = MyObserver::allocate(InitMyObserver {
ran: AtomicBool::new(false),
});
let rv = unsafe {
observer.Observe(x.coerce(),
cstr!("some-topic").as_ptr(),
ptr::null())
};
assert!(rv.succeeded());
```
The implementation has an (auto-generated) `allocate` method that takes in an
initialization struct, and returns a `RefPtr` to the instance.
`coerce` casts any XPCOM object to one of its base interfaces; in this case,
the base interface is `nsISupports`. In C++, this would be handled
automatically through inheritance, but Rust doesnt have inheritance, so the
conversion must be explicit.
## Bigger examples
The following XPCOM components are written in Rust.
- [kvstore](https://searchfox.org/mozilla-central/source/toolkit/components/kvstore),
which exposes the LMDB key-value store (via the [Rkv
library](https://docs.rs/rkv)) The API is asynchronous, using `moz_task` to
schedule all I/O on a background thread, and supports getting, setting, and
iterating over keys.
- [cert_storage](https://searchfox.org/mozilla-central/source/security/manager/ssl/cert_storage),
which stores lists of [revoked intermediate certificates](https://blog.mozilla.org/security/2015/03/03/revoking-intermediate-certificates-introducing-onecrl/).
- [bookmark_sync](https://searchfox.org/mozilla-central/source/toolkit/components/places/bookmark_sync),
which [merges](https://mozilla.github.io/dogear) bookmarks from Firefox Sync
with bookmarks in the Places database.
- [webext_storage_bridge](https://searchfox.org/mozilla-central/source/toolkit/components/extensions/storage/webext_storage_bridge),
which powers the WebExtension storage.sync API. It's a self-contained example
that pulls in a crate from application-services for the heavy lifting, wraps
that up in a Rust XPCOM component, and then wraps the component in a JS
interface. There's also some boilerplate there around adding a
`components.conf` file, and a dummy C++ header that declares the component
constructor.
- [firefox-accounts-bridge](https://searchfox.org/mozilla-central/source/services/fxaccounts/rust-bridge/firefox-accounts-bridge),
which wraps the Rust Firefox Accounts client with which we eventually want to
replace our creaky JS implementation. It has a lot of the same patterns as
webext_storage_bridge.