forked from mozilla/gecko-dev
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bug 1648348 - Create "Writing Rust Code" docs. r=froydnj,zbraniecki,l…
…ina. 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
- Loading branch information
1 parent
ba1e98b
commit 977d5b8
Showing
8 changed files
with
478 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: | ||
* | ||
``` |
Oops, something went wrong.