зеркало из https://github.com/mozilla/gecko-dev.git
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
This commit is contained in:
Родитель
ba1e98b7ca
Коммит
977d5b8a57
|
@ -4,9 +4,12 @@
|
|||
Including Rust Code in Firefox
|
||||
==============================
|
||||
|
||||
The build system has support for building, linking, and vendoring Rust crates.
|
||||
It is straightforward to take an existing Rust crate and integrate it into
|
||||
Firefox.
|
||||
This page explains how to add, build, link, and vendor Rust crates.
|
||||
|
||||
The `code documentation <../../writing-rust-code>`_ explains how to write and
|
||||
work with Rust code in Firefox. The
|
||||
`test documentation <../../testing-rust-code>`_ explains how to test and debug
|
||||
Rust code in Firefox.
|
||||
|
||||
Linking Rust crates into libxul
|
||||
===============================
|
||||
|
|
|
@ -24,6 +24,7 @@ categories:
|
|||
- uriloader
|
||||
- widget/cocoa
|
||||
- code-quality
|
||||
- writing-rust-code
|
||||
build_doc:
|
||||
- mach
|
||||
- tools/try
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# Testing & Debugging Rust Code
|
||||
|
||||
This page explains how to test and debug Rust code in Firefox.
|
||||
|
||||
The [build documentation](../build/buildsystem/rust.html) explains how to add
|
||||
new Rust code to Firefox. The [code documentation](../writing-rust-code)
|
||||
explains how to write and work with Rust code in Firefox.
|
||||
|
||||
## Testing Mozilla crates
|
||||
|
||||
Rust code will naturally be tested as part of system tests such as Mochitests.
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
# 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 service getters are generated by
|
||||
[xpcom/build/Services.py](https://searchfox.org/mozilla-central/source/xpcom/build/Services.py)
|
||||
and can be called like this:
|
||||
```
|
||||
let pref_service = xpcom::services::get_PrefService();
|
||||
```
|
||||
- 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.
|
|
@ -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/).
|
|
@ -0,0 +1,17 @@
|
|||
# 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.html) explains how to add
|
||||
new Rust code to Firefox. The [test documentation](../testing-rust-code)
|
||||
explains how to test and debug Rust code in Firefox.
|
||||
|
||||
```eval_rst
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
*
|
||||
```
|
|
@ -0,0 +1,123 @@
|
|||
# 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 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.
|
||||
- [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.
|
|
@ -200,6 +200,8 @@ SPHINX_TREES['code-quality'] = 'docs/code-quality'
|
|||
|
||||
SPHINX_TREES['testing-rust-code'] = 'docs/testing-rust-code'
|
||||
|
||||
SPHINX_TREES['writing-rust-code'] = 'docs/writing-rust-code'
|
||||
|
||||
SPHINX_TREES['bug-mgmt'] = 'docs/bug-mgmt'
|
||||
|
||||
SPHINX_TREES['setup'] = 'docs/setup'
|
||||
|
|
Загрузка…
Ссылка в новой задаче