Bug 1756218 - run android reftest/crashtest as no-fission. r=releng-reviewers,gbrown
[gecko.git] / docs / writing-rust-code / ffi.md
blobf10fad422198b41161d356d707efc61a68477a73
1 # Rust/C++ interop
3 This document describes how to use FFI in Firefox to get Rust code and C++ code to interoperate.
5 ## Transferable types
7 Generally speaking, the more complicated is the data you want to transfer, the
8 harder it'll be to transfer across the FFI boundary.
10 Booleans, integers, and pointers cause little trouble.
11 - C++ `bool` matches Rust `bool`
12 - C++ `uint8_t` matches Rust `u8`, `int32_t` matches Rust `i32`, etc.
13 - C++ `const T*` matches Rust `*const T`, `T*` matches Rust `*mut T`.
15 Lists are handled by C++ `nsTArray` and Rust `ThinVec`.
17 For strings, it is best to use the `nsstring` helper crate. Using a raw pointer
18 plus length is also possible for strings, but more error-prone.
20 If you need a hashmap, you'll likely want to decompose it into two lists (keys
21 and values) and transfer them separately.
23 Other types can be handled with tools that generate bindings, as the following
24 sections describe.
26 ## Accessing C++ code and data from Rust
28 To call a C++ function from Rust requires adding a function declaration to Rust.
29 For example, for this C++ function:
31 ```
32 extern "C" {
33 bool UniquelyNamedFunction(const nsCString* aInput, nsCString* aRetVal) {
34   return true;
37 ```
38 add this declaration to the Rust code:
39 ```rust
40 extern "C" {
41     pub fn UniquelyNamedFunction(input: &nsCString, ret_val: &mut nsCString) -> bool;
43 ```
45 Rust code can now call `UniquelyNamedFunction()` within an `unsafe` block. Note
46 that if the declarations do not match (e.g. because the C++ function signature
47 changes without the Rust declaration being updated) crashes are likely. (Hence
48 the `unsafe` block.)
50 Because of this unsafety, for non-trivial interfaces (in particular when C++
51 structs and classes must be accessed from Rust code) it's common to use
52 [rust-bindgen](https://github.com/rust-lang/rust-bindgen), which generates Rust
53 bindings. The documentation is
54 [here](https://rust-lang.github.io/rust-bindgen/).
56 ## Accessing Rust code and data from C++
58 A common option for accessing Rust code and data from C++ is to use
59 [cbindgen](https://github.com/eqrion/cbindgen), which generates C++ header
60 files. for Rust crates that expose a public C API. cbindgen is a very powerful
61 tool, and this section only covers some basic uses of it.
63 ### Basics
65 First, add suitable definitions to your Rust. `#[no_mangle]` and `extern "C"`
66 are required.
68 ```rust
69 #[no_mangle]
70 pub unsafe extern "C" fn unic_langid_canonicalize(
71     langid: &nsCString,
72     ret_val: &mut nsCString
73 ) -> bool {
74     ret_val.assign("new value");
75     true
77 ```
79 Then, add a `cbindgen.toml` file in the root of your crate. It may look like this:
81 ```toml
82 header = """/* This Source Code Form is subject to the terms of the Mozilla Public
83  * License, v. 2.0. If a copy of the MPL was not distributed with this
84  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
85 autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */
86 #ifndef mozilla_intl_locale_MozLocaleBindings_h
87 #error "Don't include this file directly, instead include MozLocaleBindings.h"
88 #endif
89 """
90 include_version = true
91 braces = "SameLine"
92 line_length = 100
93 tab_width = 2
94 language = "C++"
95 # Put FFI calls in the `mozilla::intl::ffi` namespace.
96 namespaces = ["mozilla", "intl", "ffi"]
98 # Export `ThinVec` references as `nsTArray`.
99 [export.rename]
100 "ThinVec" = "nsTArray"
103 Next, extend the relevant `moz.build` file to invoke cbindgen.
105 ```python
106 if CONFIG['COMPILE_ENVIRONMENT']:
107     CbindgenHeader('unic_langid_ffi_generated.h',
108                    inputs=['/intl/locale/rust/unic-langid-ffi'])
110     EXPORTS.mozilla.intl += [
111         '!unic_langid_ffi_generated.h',
112     ]
115 This tells the build system to run cbindgen on
116 `intl/locale/rust/unic-langid-ffi` to generate `unic_langid_ffi_generated.h`,
117 which will be placed in `$OBJDIR/dist/include/mozilla/intl/`.
119 Finally, include the generated header into a C++ file and call the function.
121 ```c++
122 #include "mozilla/intl/unic_langid_ffi_generated.h"
124 using namespace mozilla::intl::ffi;
126 void Locale::MyFunction(nsCString& aInput) const {
127   nsCString result;
128   unic_langid_canonicalize(aInput, &result);
132 ### Complex types
134 Many complex Rust types can be exposed to C++, and cbindgen will generate
135 appropriate bindings for all `pub` types. For example:
137 ```rust
138 #[repr(C)]
139 pub enum FluentPlatform {
140     Linux,
141     Windows,
142     Macos,
143     Android,
144     Other,
147 extern "C" {
148     pub fn FluentBuiltInGetPlatform() -> FluentPlatform;
152 ```c++
153 ffi::FluentPlatform FluentBuiltInGetPlatform() {
154   return ffi::FluentPlatform::Linux;
158 For an example using cbindgen to expose much more complex Rust types to C++,
159 see [this blog post].
161 [this blog post]: https://crisal.io/words/2020/02/28/C++-rust-ffi-patterns-1-complex-data-structures.html
163 ### Instances
165 If you need to create and destroy a Rust struct from C++ code, the following
166 example may be helpful.
168 First, define constructor, destructor and getter functions in Rust. (C++
169 declarations for these will be generated by cbindgen.)
171 ```rust
172 #[no_mangle]
173 pub unsafe extern "C" fn unic_langid_new() -> *mut LanguageIdentifier {
174     let langid = LanguageIdentifier::default();
175     Box::into_raw(Box::new(langid))
178 #[no_mangle]
179 pub unsafe extern "C" fn unic_langid_destroy(langid: *mut LanguageIdentifier) {
180     drop(Box::from_raw(langid));
183 #[no_mangle]
184 pub unsafe extern "C" fn unic_langid_as_string(
185     langid: &mut LanguageIdentifier,
186     ret_val: &mut nsACString,
187 ) {
188     ret_val.assign(&langid.to_string());
192 Next, in a C++ header define a destructor via `DefaultDelete`.
194 ```c++
195 #include "mozilla/intl/unic_langid_ffi_generated.h"
196 #include "mozilla/UniquePtr.h"
198 namespace mozilla {
200 template <>
201 class DefaultDelete<intl::ffi::LanguageIdentifier> {
202  public:
203   void operator()(intl::ffi::LanguageIdentifier* aPtr) const {
204     unic_langid_destroy(aPtr);
205   }
208 }  // namespace mozilla
211 (This definition must be visible any place where
212 `UniquePtr<intl::ffi::LanguageIdentifier>` is used, otherwise C++ will try to
213 free the code, which might lead to strange behaviour!)
215 Finally, implement the class.
217 ```c++
218 class Locale {
219 public:
220   explicit Locale(const nsACString& aLocale)
221     : mRaw(unic_langid_new()) {}
223   const nsCString Locale::AsString() const {
224     nsCString tag;
225     unic_langid_as_string(mRaw.get(), &tag);
226     return tag;
227   }
229 private:
230   UniquePtr<ffi::LanguageIdentifier> mRaw;
234 This makes it possible to instantiate a `Locale` object and call `AsString()`,
235 all from C++ code.
237 ## Other examples
239 For a detailed explanation of an interface in Firefox that doesn't use cbindgen
240 or rust-bindgen, see [this blog post](https://hsivonen.fi/modern-cpp-in-rust/).