Expand description
§FFI Support
This crate implements a support library to simplify implementing the patterns that the
mozilla/application-services repository uses for it’s “Rust Component” FFI libraries.
It is strongly encouraged that anybody writing FFI code in this repository read this documentation before doing so, as it is a subtle, difficult, and error prone process.
§Terminology
For each library, there are currently three parts we’re concerned with. There’s no clear correct name for these, so this documentation will attempt to use the following terminology:
-
Rust Component: A Rust crate which does not expose an FFI directly, but may be may be wrapped by one that does. These have a
crate-typein their Cargo.toml (see https://doc.rust-lang.org/reference/linkage.html) oflib, and notstaticliborcdylib(Note thatlibis the default ifcrate-typeis not specified). Examples include thefxa-client, andloginscrates. -
FFI Component: A wrapper crate that takes a Rust component, and exposes an FFI from it. These typically have
ffiin the name, and havecrate-type = ["lib", "staticlib", "cdylib"]in their Cargo.toml. For example, thefxa-client/ffiandlogins/fficrates (note: paths are subject to change). When built, these produce a native library that is consumed by the “FFI Consumer”. -
FFI Consumer: This is a low level library, typically implemented in Kotlin (for Android) or Swift (for iOS), that exposes a memory-safe wrapper around the memory-unsafe C API produced by the FFI component. It’s expected that the maintainers of the FFI Component and FFI Consumer be the same (or at least, the author of the consumer should be completely comfortable with the API exposed by, and code in the FFI component), since the code in these is extremely tightly coupled, and very easy to get wrong.
Note that while there are three parts, there may be more than three libraries relevant here, for example there may be more than one FFI consumer (one for Android, one for iOS).
§Usage
This library will typically be used in both the Rust component, and the FFI component, however it frequently will be an optional dependency in the Rust component that’s only available when a feature flag (which the FFI component will always require) is used.
The reason it’s required inside the Rust component (and not solely in the FFI component, which
would be nice), is so that types provided by that crate may implement the traits provided by
this crate (this is because Rust does not allow crate C to implement a trait defined in crate
A for a type defined in crate B).
In general, examples should be provided for the most important types and functions
(call_with_result, IntoFfi,
ExternError, etc), but you should also look at the code of
consumers of this library.
§Usage in the Rust Component
Inside the Rust component, you will implement:
-
IntoFfifor all types defined in that crate that you want to return over the FFI. For most common cases, theimplement_into_ffi_by_json!andimplement_into_ffi_by_protobuf!macros will do the job here, however you can see that trait’s documentation for discussion and examples of implementing it manually. -
Conversion to
ExternErrorfor the error type(s) exposed by that rust component, that is,impl From<MyError> for ExternError.
§Usage in the FFI Component
Inside the FFI component, you will use this library in a few ways:
-
Destructors will be exposed for each types that had
implement_into_ffi_by_pointer!called on it (usingdefine_box_destructor!), and a destructor for strings should be exposed as well, usingdefine_string_destructor -
The body of every / nearly every FFI function will be wrapped in either a
call_with_resultorcall_with_output.This is required because if we
panic!(e.g. from anassert!,unwrap(),expect(), from indexing past the end of an array, etc) across the FFI boundary, the behavior is undefined and in practice very weird things tend to happen (we aren’t caught by the caller, since they don’t have the same exception behavior as us).If you don’t think your program (or possibly just certain calls) can handle panics, you may also use the versions of these functions in the
abort_on_panicmodule, which do as their name suggest.
Additionally, c strings that are passed in as arguments may be represented using FfiStr,
which contains several helpful inherent methods for extracting their data.
Re-exports§
pub use crate::handle_map::ConcurrentHandleMap;pub use crate::handle_map::Handle;pub use crate::handle_map::HandleError;pub use crate::handle_map::HandleMap;
Modules§
- abort_
on_ panic - This module exists just to expose a variant of
call_with_resultandcall_with_outputthat aborts, instead of unwinding, on panic. - handle_
map - This module provides a
Handletype, which you can think of something like a dynamically checked, type erased reference/pointer type. Depending on the usage pattern a handle can behave as either a borrowed reference, or an owned pointer.
Macros§
- define_
box_ destructor - Define a (public) destructor for a type that was allocated by
Box::into_raw(Box::new(value))(e.g. a pointer which is probably opaque). - define_
bytebuffer_ destructor - Define a (public) destructor for the
ByteBuffertype. - define_
handle_ map_ deleter - Define a (public) destructor for a type that lives inside a lazy_static
ConcurrentHandleMap. - define_
string_ destructor - For a number of reasons (name collisions are a big one, but, it also wouldn’t work on all
platforms), we cannot export
extern "C"functions from this library. However, it’s pretty common to want to free strings allocated by rust, so many libraries will need this, so we provide it as a macro. - implement_
into_ ffi_ by_ delegation - Implement
InfoFfifor a type by converting through another type. - implement_
into_ ffi_ by_ json - Implements
IntoFfifor the provided types (more than one may be passed in) by converting to the type to a JSON string. - implement_
into_ ffi_ by_ pointer - Implements
IntoFfifor the provided types (more than one may be passed in) by allocating$Ton the heap as an opaque pointer. - implement_
into_ ffi_ by_ protobuf - Implements
IntoFfifor the provided types (more than one may be passed in) implementingprost::Message(protobuf auto-generated type) by converting to the type to aByteBuffer. ThisByteBuffershould later be passed by value. - static_
assert - Force a compile error if the condition is not met. Requires a unique name for the assertion for… reasons. This is included mainly because it’s a common desire for FFI code, but not for other sorts of code.
Structs§
- Byte
Buffer - ByteBuffer is a struct that represents an array of bytes to be sent over the FFI boundaries. There are several cases when you might want to use this, but the primary one for us is for returning protobuf-encoded data to Swift and Java. The type is currently rather limited (implementing almost no functionality), however in the future it may be more expanded.
- Error
Code - A wrapper around error codes, which is represented identically to an i32 on the other side of the FFI. Essentially exists to check that we don’t accidentally reuse success/panic codes for other things.
- Extern
Error - Represents an error that occured within rust, storing both an error code, and additional data that may be used by the caller.
- FfiStr
FfiStr<'a>is a safe (#[repr(transparent)]) wrapper around a nul-terminated*const c_char(e.g. a C string). Conceptually, it is similar tostd::ffi::CStr, except that it may be used in the signatures of extern “C” functions.
Traits§
- IntoFfi
- This trait is used to return types over the FFI. It essentially is a mapping between a type and
version of that type we can pass back to C (
IntoFfi::Value).
Functions§
- call_
with_ output - Call a callback that returns a
Twhile: - call_
with_ result - Call a callback that returns a
Result<T, E>while: - destroy_
c_ ⚠string - Free the memory of a string created by
rust_string_to_con the rust heap. Ifc_stringis null, this is a no-op. - ensure_
panic_ hook_ is_ setup - Initialize our panic handling hook to optionally log panics
- opt_
rust_ ⚠str_ from_ c Deprecated - Same as
rust_string_from_c, but returns None ifc_stringis null instead of asserting. - opt_
rust_ ⚠string_ from_ c Deprecated - Same as
rust_string_from_c, but returns None ifc_stringis null instead of asserting. - opt_
rust_ string_ to_ c - Variant of
rust_string_to_cwhich takes an Option, and returns null for None. - rust_
str_ ⚠from_ c Deprecated - Convert a null-terminated C string to a rust
str. This does not take ownership of the string, and you should be careful about the lifetime of the resulting string. Note that strings containing invalid UTF-8 are replaced with the empty string (for many cases, you will want to userust_string_from_cinstead, which will do a lossy conversion). - rust_
string_ ⚠from_ c Deprecated - Convert a null-terminated C into an owned rust string, replacing invalid UTF-8 with the unicode replacement character.
- rust_
string_ to_ c - Convert a rust string into a NUL-terminated utf-8 string suitable for passing to C, or to things ABI-compatible with C.