diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /docs/writing-rust-code | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'docs/writing-rust-code')
-rw-r--r-- | docs/writing-rust-code/basics.md | 84 | ||||
-rw-r--r-- | docs/writing-rust-code/cpp-interop.md | 240 | ||||
-rw-r--r-- | docs/writing-rust-code/index.md | 16 | ||||
-rw-r--r-- | docs/writing-rust-code/uniffi.md | 70 | ||||
-rw-r--r-- | docs/writing-rust-code/update-policy.md | 150 | ||||
-rw-r--r-- | docs/writing-rust-code/xpcom.md | 120 |
6 files changed, 680 insertions, 0 deletions
diff --git a/docs/writing-rust-code/basics.md b/docs/writing-rust-code/basics.md new file mode 100644 index 0000000000..d91288133b --- /dev/null +++ b/docs/writing-rust-code/basics.md @@ -0,0 +1,84 @@ +# Basics + +## Formatting Rust code + +To format all the Rust code within a directory `$DIR`, run: +``` +./mach lint -l rustfmt --fix $DIR +``` + +## Using Cargo + +Many Cargo commands can be run on individual crates. Change into the directory +containing the crate's `Cargo.toml` file, and then run the command with +`MOZ_TOPOBJDIR` set appropriately. For example, to generate and view rustdocs +for the `xpcom` crate, run these commands: + +``` +cd xpcom/rust/xpcom +MOZ_TOPOBJDIR=$OBJDIR cargo doc +cd - +firefox target/doc/xpcom/index.html +``` +where `$OBJDIR` is the path to the object directory. + +## Using static prefs + +Static boolean/integer prefs can be easily accessed from Rust code. Add a +`rust: true` field to the pref definition in +[modules/libpref/init/StaticPrefList.yaml](https://searchfox.org/mozilla-central/source/modules/libpref/init/StaticPrefList.yaml), +like this: +```yaml +- name: my.lucky.pref + type: RelaxedAtomicBool + value: true + mirror: always + rust: true +``` +The pref can then be accessed via the `pref!` macro, like this: +``` +let my_lucky_pref = static_prefs::pref!("my.lucky.pref"); +``` + +## Helper crates + +The following in-tree helper crates provide idiomatic support for some common patterns. +- [nserror](https://searchfox.org/mozilla-central/source/xpcom/rust/nserror/src/lib.rs) +reflects `nsresult` codes into Rust. +- [nsstring](https://searchfox.org/mozilla-central/source/xpcom/rust/nsstring/src/lib.rs) + exposes bindings for XPCOM string types. You can use the same `ns{A,C}String` + types as C++ for owned strings and pass them back and forth over the + boundary. There is also `ns{A,C}Str` for dependent or borrowed strings. +- [xpcom](https://searchfox.org/mozilla-central/source/xpcom/rust/xpcom/src) + provides multiple building blocks for a component's implementation. + - The `RefPtr` type is for managing reference-counted pointers. + - XPCOM component getters are generated by + [xpcom/components/gen_static_components.py](https://searchfox.org/mozilla-central/source/xpcom/components/gen_static_components.py), + and can be called like this: + ``` + use xpcom::{interfaces::nsIPrefService, RefPtr}; + let pref_service: RefPtr<nsIPrefService> = xpcom::components::Preferences::service()?; + ``` + - There is also a `get_service` function that works like `do_GetService` in + C++, as an alternative. + - A set of `derive` macros help with declaring interface implementations. The + [docs](https://searchfox.org/mozilla-central/source/xpcom/rust/xpcom/xpcom_macros/src/lib.rs) + have details and examples. +- [moz_task](https://searchfox.org/mozilla-central/source/xpcom/rust/moz_task/src/lib.rs) + wraps XPCOM's threading functions in order to make it easy and safe to write + threaded code. It has helpers for getting and creating threads, dispatching + async runnables, and thread-safe handles. +- [storage](https://searchfox.org/mozilla-central/source/storage/rust/src/lib.rs) + is an interface to mozStorage, our wrapper for SQLite. It can wrap an + existing storage connection, and prepare and execute statements. This crate + wraps the synchronous connection API, and lets you execute statements + asynchronously via `moz_task`. +- [storage_variant](https://searchfox.org/mozilla-central/source/storage/variant/src/lib.rs) + is for working with variants. It also provides a `HashPropertyBag` type + that's useful for passing hash maps over XPCOM to JS. + +Unfortunately, rustdocs are [not yet generated and +hosted](https://bugzilla.mozilla.org/show_bug.cgi?id=1428139) for crates within +mozilla-central. Therefore, the crate links shown above link to files +containing the relevant rustdocs source where possible. However, you can +generate docs locally using the `cargo doc` command described above. diff --git a/docs/writing-rust-code/cpp-interop.md b/docs/writing-rust-code/cpp-interop.md new file mode 100644 index 0000000000..f10fad4221 --- /dev/null +++ b/docs/writing-rust-code/cpp-interop.md @@ -0,0 +1,240 @@ +# Rust/C++ interop + +This document describes how to use FFI in Firefox to get Rust code and C++ code to interoperate. + +## Transferable types + +Generally speaking, the more complicated is the data you want to transfer, the +harder it'll be to transfer across the FFI boundary. + +Booleans, integers, and pointers cause little trouble. +- C++ `bool` matches Rust `bool` +- C++ `uint8_t` matches Rust `u8`, `int32_t` matches Rust `i32`, etc. +- C++ `const T*` matches Rust `*const T`, `T*` matches Rust `*mut T`. + +Lists are handled by C++ `nsTArray` and Rust `ThinVec`. + +For strings, it is best to use the `nsstring` helper crate. Using a raw pointer +plus length is also possible for strings, but more error-prone. + +If you need a hashmap, you'll likely want to decompose it into two lists (keys +and values) and transfer them separately. + +Other types can be handled with tools that generate bindings, as the following +sections describe. + +## Accessing C++ code and data from Rust + +To call a C++ function from Rust requires adding a function declaration to Rust. +For example, for this C++ function: + +``` +extern "C" { +bool UniquelyNamedFunction(const nsCString* aInput, nsCString* aRetVal) { + return true; +} +} +``` +add this declaration to the Rust code: +```rust +extern "C" { + pub fn UniquelyNamedFunction(input: &nsCString, ret_val: &mut nsCString) -> bool; +} +``` + +Rust code can now call `UniquelyNamedFunction()` within an `unsafe` block. Note +that if the declarations do not match (e.g. because the C++ function signature +changes without the Rust declaration being updated) crashes are likely. (Hence +the `unsafe` block.) + +Because of this unsafety, for non-trivial interfaces (in particular when C++ +structs and classes must be accessed from Rust code) it's common to use +[rust-bindgen](https://github.com/rust-lang/rust-bindgen), which generates Rust +bindings. The documentation is +[here](https://rust-lang.github.io/rust-bindgen/). + +## Accessing Rust code and data from C++ + +A common option for accessing Rust code and data from C++ is to use +[cbindgen](https://github.com/eqrion/cbindgen), which generates C++ header +files. for Rust crates that expose a public C API. cbindgen is a very powerful +tool, and this section only covers some basic uses of it. + +### Basics + +First, add suitable definitions to your Rust. `#[no_mangle]` and `extern "C"` +are required. + +```rust +#[no_mangle] +pub unsafe extern "C" fn unic_langid_canonicalize( + langid: &nsCString, + ret_val: &mut nsCString +) -> bool { + ret_val.assign("new value"); + true +} +``` + +Then, add a `cbindgen.toml` file in the root of your crate. It may look like this: + +```toml +header = """/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */""" +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */ +#ifndef mozilla_intl_locale_MozLocaleBindings_h +#error "Don't include this file directly, instead include MozLocaleBindings.h" +#endif +""" +include_version = true +braces = "SameLine" +line_length = 100 +tab_width = 2 +language = "C++" +# Put FFI calls in the `mozilla::intl::ffi` namespace. +namespaces = ["mozilla", "intl", "ffi"] + +# Export `ThinVec` references as `nsTArray`. +[export.rename] +"ThinVec" = "nsTArray" +``` + +Next, extend the relevant `moz.build` file to invoke cbindgen. + +```python +if CONFIG['COMPILE_ENVIRONMENT']: + CbindgenHeader('unic_langid_ffi_generated.h', + inputs=['/intl/locale/rust/unic-langid-ffi']) + + EXPORTS.mozilla.intl += [ + '!unic_langid_ffi_generated.h', + ] +``` + +This tells the build system to run cbindgen on +`intl/locale/rust/unic-langid-ffi` to generate `unic_langid_ffi_generated.h`, +which will be placed in `$OBJDIR/dist/include/mozilla/intl/`. + +Finally, include the generated header into a C++ file and call the function. + +```c++ +#include "mozilla/intl/unic_langid_ffi_generated.h" + +using namespace mozilla::intl::ffi; + +void Locale::MyFunction(nsCString& aInput) const { + nsCString result; + unic_langid_canonicalize(aInput, &result); +} +``` + +### Complex types + +Many complex Rust types can be exposed to C++, and cbindgen will generate +appropriate bindings for all `pub` types. For example: + +```rust +#[repr(C)] +pub enum FluentPlatform { + Linux, + Windows, + Macos, + Android, + Other, +} + +extern "C" { + pub fn FluentBuiltInGetPlatform() -> FluentPlatform; +} +``` + +```c++ +ffi::FluentPlatform FluentBuiltInGetPlatform() { + return ffi::FluentPlatform::Linux; +} +``` + +For an example using cbindgen to expose much more complex Rust types to C++, +see [this blog post]. + +[this blog post]: https://crisal.io/words/2020/02/28/C++-rust-ffi-patterns-1-complex-data-structures.html + +### Instances + +If you need to create and destroy a Rust struct from C++ code, the following +example may be helpful. + +First, define constructor, destructor and getter functions in Rust. (C++ +declarations for these will be generated by cbindgen.) + +```rust +#[no_mangle] +pub unsafe extern "C" fn unic_langid_new() -> *mut LanguageIdentifier { + let langid = LanguageIdentifier::default(); + Box::into_raw(Box::new(langid)) +} + +#[no_mangle] +pub unsafe extern "C" fn unic_langid_destroy(langid: *mut LanguageIdentifier) { + drop(Box::from_raw(langid)); +} + +#[no_mangle] +pub unsafe extern "C" fn unic_langid_as_string( + langid: &mut LanguageIdentifier, + ret_val: &mut nsACString, +) { + ret_val.assign(&langid.to_string()); +} +``` + +Next, in a C++ header define a destructor via `DefaultDelete`. + +```c++ +#include "mozilla/intl/unic_langid_ffi_generated.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +template <> +class DefaultDelete<intl::ffi::LanguageIdentifier> { + public: + void operator()(intl::ffi::LanguageIdentifier* aPtr) const { + unic_langid_destroy(aPtr); + } +}; + +} // namespace mozilla +``` + +(This definition must be visible any place where +`UniquePtr<intl::ffi::LanguageIdentifier>` is used, otherwise C++ will try to +free the code, which might lead to strange behaviour!) + +Finally, implement the class. + +```c++ +class Locale { +public: + explicit Locale(const nsACString& aLocale) + : mRaw(unic_langid_new()) {} + + const nsCString Locale::AsString() const { + nsCString tag; + unic_langid_as_string(mRaw.get(), &tag); + return tag; + } + +private: + UniquePtr<ffi::LanguageIdentifier> mRaw; +} +``` + +This makes it possible to instantiate a `Locale` object and call `AsString()`, +all from C++ code. + +## Other examples + +For a detailed explanation of an interface in Firefox that doesn't use cbindgen +or rust-bindgen, see [this blog post](https://hsivonen.fi/modern-cpp-in-rust/). diff --git a/docs/writing-rust-code/index.md b/docs/writing-rust-code/index.md new file mode 100644 index 0000000000..721a9be811 --- /dev/null +++ b/docs/writing-rust-code/index.md @@ -0,0 +1,16 @@ +# Writing Rust Code + +This page explains how to write and work with Rust code in Firefox, with an +emphasis on interoperation with C++ code. + +The [build documentation](/build/buildsystem/rust.rst) explains how to add +new Rust code to Firefox. The [test documentation](/testing-rust-code/index.md) +explains how to test and debug Rust code in Firefox. + +```{toctree} +:titlesonly: +:maxdepth: 1 +:glob: + +* +``` diff --git a/docs/writing-rust-code/uniffi.md b/docs/writing-rust-code/uniffi.md new file mode 100644 index 0000000000..56ffc0935a --- /dev/null +++ b/docs/writing-rust-code/uniffi.md @@ -0,0 +1,70 @@ +# Generating Javascript bindings with UniFFI + +Firefox supports auto-generating JS bindings for Rust components using [UniFFI](https://mozilla.github.io/uniffi-rs/). + +## How it works + +The Rust crate contains a +[UniFFI Definition Language (UDL) file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html), which describes the +interface to generate bindings for. + +The UniFFI core generates the scaffolding: Rust code which acts as the FFI layer from the UDL file. The functions of +this layer all use the C calling convention and all structs use a C layout, this is the de facto standard for FFI +interoperability. + +The [`uniffi-bindgen-gecko-js`](https://searchfox.org/mozilla-central/source/toolkit/components/uniffi-bindgen-gecko-js) +tool, which lives in the Firefox source tree, generates 2 things: + - A JS interface for the scaffolding code, which uses [WebIDL](/dom/bindings/webidl/index.rst) + - A module that uses the scaffolding to provide the bindings API. + +Currently, this generated code gets checked in to source control. We are working on a system to avoid this and +auto-generate it at build time instead (see [bugzilla 1756214](https://bugzilla.mozilla.org/show_bug.cgi?id=1756214)). + +## Before creating new bindings with UniFFI + +Keep a few things in mind before you create a new set of bindings: + + - **UniFFI was not written to maximize performance.** It's code is efficient enough to handle many use cases, but at this + point should probably be avoided for performance critical components. + - **uniffi-bindgen-gecko-js bindings run with chrome privileges.** Make sure this is acceptable for your project + - **Only a subset of Rust types can be exposed via the FFI.** Check the [UniFFI Book](https://mozilla.github.io/uniffi-rs/) to see what + types are compatible with UniFFI. + +If any of these are blockers for your work, consider discussing it further with the UniFFI devs to see if we can support +your project: + + - Chat with us on `#uniffi` on Matrix/Element + - File an issue on [mozilla/uniffi](https://github.com/mozilla/uniffi-rs/) + +## Creating new bindings with UniFFI + +You can see an example of this feature in use: [when application-services swapped the tabs js sync engine with rust](https://bugzilla.mozilla.org/show_bug.cgi?id=1791851) + +Here's how you can create a new set of bindings using UniFFI: + + 1. UniFFI your crate (if it isn't already): + - [Create a UDL file to describe your interface](https://mozilla.github.io/uniffi-rs/udl_file_spec.html) + - [Set up your Rust crate to generate scaffolding](https://mozilla.github.io/uniffi-rs/tutorial/Rust_scaffolding.html) + 2. Add your crate as a Firefox dependency (if it isn't already) + - **If the code will exist in the mozilla-central repo:** + - Create a new directory for the Rust crate + - Edit `toolkit/library/rust/shared/Cargo.toml` and add a dependency to your library path + - **If the code exists in an external repo:** + - Edit `toolkit/library/rust/shared/Cargo.toml` and add a dependency to your library URL + - Run `mach vendor rust` to vendor in your Rust code + 3. Generate bindings code for your crate + - Add the path of your UDL (that you made in step 1) in `toolkit/components/uniffi-bindgen-gecko-js/mach_commands.py` + - Run `./mach uniffi generate` + - add your newly generated `Rust{udl-name}.sys.mjs` file to `toolkit/components/uniffi-bindgen-gecko-js/components/moz.build` + - Then simply import your module to the file you want to use it in and start using your APIs! + + Example from tabs module: + + ``` js + ChromeUtils.defineESModuleGetters(lazy, { + ... + TabsStore: "resource://gre/modules/RustTabs.sys.mjs", + }); + ... + this._rustStore = await lazy.TabsStore.init(path); + ``` diff --git a/docs/writing-rust-code/update-policy.md b/docs/writing-rust-code/update-policy.md new file mode 100644 index 0000000000..27d575a36e --- /dev/null +++ b/docs/writing-rust-code/update-policy.md @@ -0,0 +1,150 @@ +# Rust Update Policy + +We document here the decision making and planning around when we update the +Rust toolchains used and required to build Firefox. + +This allows contributors to know when new features will be usable, and +downstream packagers to know what toolchain will be required for each Firefox +release. Both benefit from the predictability of a schedule. + +## Policy + +### Official builds + +_We ship official stable Firefox with a stable Rust._ + +As a general rule, we update the Rust version used to build Firefox Nightly +soon after its release, unless it's less than 7 days away from a soft-freeze, +in which case we wait for the next Nightly train. + +We don't upgrade the Rust version in the beta or release branches of Firefox. + +The following exceptions apply: + +- We may use a Rust version from the Rust beta or nightly channels for new + platforms (e.g. we did so for Android, arm64 Windows and arm64 macOS), and + later upgrade when that Rust version becomes stable (we may even do so on the + Firefox beta branch). + +- We may skip the update (or backout the update) if major problems are + encountered (typically, we've had to do so because of build problems, crash + reporting bustage, or performance issues). + +### Developer builds + +_Local developer builds use whatever Rust toolchain is available on the +system._ + +Someone building Firefox can maintain the latest stable Rust with the `rustup` +or `mach bootstrap` tools, or try other variations. + +### Minimum Supported Rust Version + +_We will update the Minimum Supported Rust Version (MSRV) when required._ + +The MSRV will generally remain unchanged, until a newer version is required +by some code. + +When that happens, we'll prefer to update the MSRV to the strict minimum +required at that moment (e.g. if we require version 1.47.0, the currently used +Rust version is 1.51.0, and a crate needs 1.50.0, we'll prefer to update the +MSRV to 1.50.0 rather than 1.51.0). + +The MSRV won't be updated to a version of Rust that hasn't been used for +Firefox Nightly for at least 14 days. + +We expect ESR releases will keep their MSRV, so backporting security fixes may +require Rust compatibility work. + +### Rationale + +Historically, the Rust ecosystem quickly required new features provided by new +Rust compilers, which made it necessary to update the minimum supported version +quite often, and as such, a scheduled update was deemed a better trade-off. + +Fast-forward several years, and new Rust compiler releases more rarely sport +ground-breaking new features, which has reduced the necessity to update quite +significantly. + +On the flip side, in some instances, we have had to stick to specific versions +of the Rust compiler for extended periods of time because of e.g. regressions, +going against the schedule. + +## Schedule + +Here are the Rust versions for each Firefox version. + +- The "Uses" column indicates the version of Rust used to build + releases shipped to users. + +- The "MSRV" column indicates the minimum supported Rust version to build + the sources. + +| Firefox Version | Uses | MSRV | Rust "Uses" release date | Nightly Soft Freeze | Firefox release date | +|-----------------|------|----------|--------------------------|---------------------|----------------------| +| Firefox 56 | Rust 1.19.0 | 1.17.0 | 2017 April 27 | | 2017 September 26 +| Firefox 57 | Rust 1.19.0 | 1.19.0 | 2017 July 20 | | 2017 November 14 +| Firefox 58 | Rust 1.21.0 | 1.21.0 | 2017 October 12 | | 2018 January 16 +| Firefox 59 | Rust 1.22.1 | 1.22.1 | 2017 November 23 | | 2018 March 13 +| Firefox 60 | Rust 1.24.0 | 1.24.0 | 2018 February 15 | | 2018 May 9 +| Firefox 61 | Rust 1.24.0 | 1.24.0 | 2018 February 15 | | 2018 June 26 +| Firefox 62 | Rust 1.24.0 | 1.24.0 | 2018 February 15 | | 2018 September 5 +| Firefox 63 | Rust 1.28.0 | 1.28.0 | 2018 August 2 | | 2018 October 23 +| Firefox 64 | Rust 1.29.2 | 1.29.0 | 2018 September 13 | 2018 October 15 | 2018 December 11 +| Firefox 65 | Rust 1.30.0 | 1.30.0 | 2018 October 25 | 2018 December 3 | 2019 January 29 +| Firefox 66 | Rust 1.31.0 | 1.31.0 | 2018 December 6 | 2019 January 21 | 2019 March 19 +| Firefox 67 | Rust 1.32.0 | 1.32.0 | 2019 January 17 | 2019 March 11 | 2019 May 21 +| Firefox 68 | Rust 1.34.0 | 1.34.0 | 2019 April 11 | 2019 May 13 | 2019 July 9 +| Firefox 69 | Rust 1.35.0 | 1.35.0 | 2019 May 23 | 2019 July 1 | 2019 September 3 +| Firefox 70 | Rust 1.37.0 | 1.36.0 | 2019 July 4 | 2019 August 26 | 2019 October 22 +| Firefox 71 | Rust 1.37.0 | 1.37.0 | 2019 August 15 | 2019 October 14 | 2019 December 3 +| Firefox 72 | Rust 1.38.0 | 1.37.0 | 2019 August 15 | 2019 November 25 | 2020 January 7 +| Firefox 73 | Rust 1.39.0 | 1.39.0 | 2019 November 7 | 2020 January 1 | 2020 February 11 +| Firefox 74 | Rust 1.39.0 | 1.39.0 | 2019 November 7 | 2020 February 6 | 2020 March 10 +| Firefox 75 | Rust 1.41.0 | 1.41.0 | 2020 January 30 | 2020 March 5 | 2020 April 7 +| Firefox 76 | Rust 1.41.0 | 1.41.0 | 2020 January 30 | 2020 April 2 | 2020 May 5 +| Firefox 77 | Rust 1.41.1 | 1.41.0 | 2020 January 30 | 2020 April 30 | 2020 June 2 +| Firefox 78 | Rust 1.43.0 | 1.41.0 | 2020 April 23 | 2020 May 28 | 2020 June 30 +| Firefox 79 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 June 26 | 2020 July 28 +| Firefox 80 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 July 23 | 2020 August 25 +| Firefox 81 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 August 20 | 2020 September 22 +| Firefox 82 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 September 17 | 2020 October 20 +| Firefox 83 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 October 15 | 2020 November 17 +| Firefox 84 | Rust 1.47.0 | 1.43.0 | 2020 October 8 | 2020 November 12 | 2020 December 15 +| Firefox 85 | Rust 1.48.0 | 1.47.0 | 2020 November 19 | 2020 December 10 | 2021 January 26 +| Firefox 86 | Rust 1.49.0 | 1.47.0 | 2020 December 31 | 2021 January 21 | 2021 February 23 +| Firefox 87 | Rust 1.50.0 | 1.47.0 | 2021 February 11 | 2021 February 18 | 2021 March 23 +| Firefox 88 | Rust 1.50.0 | 1.47.0 | 2021 February 11 | 2021 March 18 | 2021 April 19 +| Firefox 89 | Rust 1.51.0 | 1.47.0 | 2021 March 25 | 2021 April 15 | 2021 June 1 +| Firefox 90 | Rust 1.52.0 | 1.47.0 | 2021 May 6 | 2021 May 27 | 2021 June 29 +| Firefox 91 | Rust 1.53.0 | 1.51.0 | 2021 June 17 | 2021 July 8 | 2021 August 10 +| Firefox 92 | Rust 1.54.0 | 1.51.0 | 2021 July 29 | 2021 August 5 | 2021 September 7 +| Firefox 93 | Rust 1.54.0 | 1.51.0 | 2021 July 29 | 2021 September 2 | 2021 October 5 +| Firefox 94 | Rust 1.55.0 | 1.53.0 | 2021 September 9 | 2021 September 30 | 2021 November 2 +| Firefox 95 | Rust 1.56.0 | 1.53.0 | 2021 October 21 | 2021 October 28 | 2021 December 7 +| Firefox 96 | Rust 1.57.0 | 1.53.0 | 2021 December 2 | 2021 December 2 | 2022 January 11 +| Firefox 97 | Rust 1.57.0 | 1.57.0 | 2021 December 2 | 2022 January 6 | 2022 February 8 +| Firefox 98 | Rust 1.58.0 | 1.57.0 | 2022 January 13 | 2022 February 2 | 2022 March 8 +| Firefox 99 | Rust 1.59.0 | 1.57.0 | 2022 February 24 | 2022 March 3 | 2022 April 5 +| Firefox 100 | Rust 1.59.0 | 1.57.0 | 2022 February 24 | 2022 March 31 | 2022 May 3 +| Firefox 101 | Rust 1.60.0 | 1.59.0 | 2022 April 7 | 2022 April 28 | 2022 May 31 +| Firefox 102 | Rust 1.60.0 | 1.59.0 | 2022 April 7 | 2022 May 26 | 2022 June 28 +| Firefox 103 | Rust 1.61.0 | 1.59.0 | 2022 May 19 | 2022 June 23 | 2022 July 27 +| Firefox 104 | Rust 1.62.0 | 1.59.0 | 2022 June 30 | 2022 July 21 | 2022 August 23 +| Firefox 105 | Rust 1.63.0 | 1.61.0 | 2022 August 11 | 2022 August 18 | 2022 September 20 +| Firefox 106 | Rust 1.63.0 | 1.61.0 | 2022 August 11 | 2022 September 15 | 2022 October 18 +| Firefox 107 | Rust 1.64.0 | 1.61.0 | 2022 September 22 | 2022 October 13 | 2022 November 15 +| Firefox 108 | Rust 1.65.0 | 1.63.0 | 2022 November 3 | 2022 November 10 | 2022 December 13 +| Firefox 109 | Rust 1.65.0 | 1.63.0 | 2022 November 3 | 2022 December 8 | 2023 January 17 +| Firefox 110 | Rust 1.66.0 | 1.65.0 | 2022 December 15 | 2023 January 12 | 2023 February 14 +| Firefox 111 | Rust 1.67.0 | 1.65.0 | 2023 January 26 | 2023 February 9 | 2023 March 14 +| Firefox 112 | Rust 1.67.0 | 1.65.0 | 2023 January 26 | 2023 March 9 | 2023 April 11 +| Firefox 113 | Rust 1.68.0 | 1.65.0 | 2023 March 9 | 2023 April 6 | 2023 May 9 +| Firefox 114 | Rust 1.69.0 | 1.65.0 | 2023 April 20 | 2023 May 4 | 2023 June 6 +| **Estimated** | +| Firefox 115 | Rust 1.69.0 | 1.66.0 | 2023 April 20 | 2023 June 1 | 2023 July 4 +| Firefox 116 | Rust 1.70.0 | ? | 2023 June 1 | 2023 June 29 | 2023 August 1 +| Firefox 117 | Rust 1.71.0 | ? | 2023 July 13 | 2023 July 27 | 2023 August 29 +| Firefox 118 | Rust 1.71.0 | ? | 2023 July 13 | 2023 August 24 | 2023 September 26 +| Firefox 119 | Rust 1.72.0 | ? | 2023 August 24 | 2023 September 21 | 2023 October 24 +| Firefox 120 | Rust 1.73.0 | ? | 2023 October 4 | 2023 October 19 | 2023 November 21 diff --git a/docs/writing-rust-code/xpcom.md b/docs/writing-rust-code/xpcom.md new file mode 100644 index 0000000000..dbe297e368 --- /dev/null +++ b/docs/writing-rust-code/xpcom.md @@ -0,0 +1,120 @@ +# 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.rst). + +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 +#[xpcom(implement(nsIObserver), atomic)] +struct MyObserver { + ran: AtomicBool, +} +``` + +This defines the implementation type, which will be refcounted in the specified +way and implement the listed xpidl interfaces. It will also declare a second +initializer struct `InitMyObserver` which can be used to allocate a new +`MyObserver` using the `MyObserver::allocate` method. + +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 u16, + ) -> 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. This is because all XPCOM +objects are reference counted (like `Arc<T>`), so cannot provide exclusive access. + +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 doesn’t 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. + [There's also some docs on how Rust interacts with Sync](/services/sync/rust-engines.rst) +- [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. [It has some in-depth documentation on how it hangs together](../toolkit/components/extensions/webextensions/webext-storage.rst). |