Trait ffi_support::IntoFfi
source · pub unsafe trait IntoFfi {
type Value;
fn ffi_default() -> Self::Value;
fn into_ffi_value(self) -> Self::Value;
}
Expand description
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
).
The main wrinkle is that we need to be able to pass a value back to C in both the success and error cases. In the error cases, we don’t want there to need to be any cleanup for the foreign code to do, and we want the API to be relatively easy to use.
Additionally, the mapping is not consistent for different types. For some rust types, we want to
convert them to JSON. For some, we want to return an opaque *mut T
handle. For others,
we’d like to return by value.
This trait supports those cases by adding some type-level indirection, and allowing both cases to be provided (both cases what is done in the error and success cases).
We implement this for the following types:
-
String
, by conversion to*mut c_char
. Note that the caller (on the other side of the FFI) is expected to free this, so you will need to provide them with a destructor for strings, which can be done with thedefine_string_destructor!
macro. -
()
: as a no-op conversion – this just allows us to expose functions without a return type over the FFI. -
bool
: is implemented by conversion tou8
(0u8
isfalse
,1u8
istrue
, andffi_default()
isfalse
). This is because it doesn’t seem to be safe to pass over the FFI directly (or at least, doing so might hit a bug in JNA). -
All numeric primitives except
isize
,usize
,char
,i128
, andu128
are implememented by passing directly through (and usingDefault::default()
forffi_default()
).isize
,usize
could be added, but they’d be quite easy to accidentally misuse, so we currently omit them.char
is less easy to misuse, but it’s also less clear why you’d want to be doing this. If we did ever add this, we’d probably want to convert to au32
(similar to how we convertbool
tou8
) for better ABI stability.i128
andu128
do not have a stable ABI, so they cannot be returned across the FFI.
-
Option<T>
whereT
isIntoFfi
, by returningIntoFfi::ffi_default()
forNone
. -
Vec<T>
whereT
isIntoFfi
andIntoFfiJsonTag
(note: you get this automatically withimplement_into_ffi_by_json!
), allowingVec<T>
to be passed back as JSON ifT
could be.- In the future, we may do this for
serde_json::Value
andHashMap<String, T>
as well.
- In the future, we may do this for
None of these are directly helpful for user types though, so macros are provided for the following cases:
-
For types which are passed around by an opaque pointer, the macro
implement_into_ffi_by_pointer!
is provided. -
For types which should be returned as a JSON string, the macro
implement_into_ffi_by_json!
is provided.
See the “Examples” section below for some other cases, such as returning by value.
Safety
This is an unsafe trait (implementing it requires unsafe impl
). This is because we cannot
guarantee that your type is safe to pass to C. The helpers we’ve provided as macros should be
safe to use, and in the cases where a common pattern can’t be done both safely and generically,
we’ve opted not to provide a macro for it. That said, many of these cases are still safe if you
meet some relatively basic requirements, see below for examples.
Examples
Returning types by value
If you want to return a type by value, we don’t provide a macro for this, primarially because
doing so cannot be statically guarantee that it is safe. However, it is safe for the cases
where the type is either #[repr(C)]
or #[repr(transparent)]
. If this doesn’t hold, you will
want to use a different option!
Regardless, if this holds, it’s fairly simple to implement, for example:
#[derive(Default)]
#[repr(C)]
pub struct Point {
pub x: i32,
pub y: i32,
}
unsafe impl IntoFfi for Point {
type Value = Self;
#[inline] fn ffi_default() -> Self { Default::default() }
#[inline] fn into_ffi_value(self) -> Self { self }
}
Conversion to another type (which is returned over the FFI)
In the FxA FFI, we have a SyncKeys
type, which is converted to a different type before
returning over the FFI. (The real FxA FFI is a little different, and more complex, but this is
relatively close, and more widely recommendable than the one the FxA FFI uses):
This is fairly easy to do by performing the conversion inside IntoFfi
.
pub struct SyncKeys(pub String, pub String);
#[repr(C)]
pub struct SyncKeysC {
pub sync_key: *mut c_char,
pub xcs: *mut c_char,
}
unsafe impl IntoFfi for SyncKeys {
type Value = SyncKeysC;
#[inline]
fn ffi_default() -> SyncKeysC {
SyncKeysC {
sync_key: ptr::null_mut(),
xcs: ptr::null_mut(),
}
}
#[inline]
fn into_ffi_value(self) -> SyncKeysC {
SyncKeysC {
sync_key: ffi_support::rust_string_to_c(self.0),
xcs: ffi_support::rust_string_to_c(self.1),
}
}
}
// Note: this type manages memory, so you still will want to expose a destructor for this,
// and possibly implement Drop as well.
Required Associated Types
sourcetype Value
type Value
This type must be:
-
Compatible with C, which is to say
#[repr(C)]
, a numeric primitive, another type that has guarantees made about it’s layout, or a#[repr(transparent)]
wrapper around one of those.One could even use
&T
, so long asT: Sized
, although it’s extremely dubious to return a reference to borrowed memory over the FFI, since it’s very difficult for the caller to know how long it remains valid. -
Capable of storing an empty/ignorable/default value.
-
Capable of storing the actual value.
Valid examples include:
-
Primitive numbers (other than i128/u128)
-
#[repr(C)] structs containing only things on this list.
-
Raw pointers:
*const T
, and*mut T
-
Enums with a fixed
repr
, although it’s a good idea avoid#[repr(C)]
enums in favor of#[repr(i32)]
(for example, any fixed type there should be fine), as it’s potentially error prone to access#[repr(C)]
enums from Android over JNA (it’s only safe if C’ssizeof(int) == 4
, which is very common, but not universally true). -
&T
/&mut T
whereT: Sized
but only if you really know what you’re doing, because this is probably a mistake.
Invalid examples include things like &str
, &[T]
, String
, Vec<T>
, Box<T>
,
std::ffi::CString
, &std::ffi::CStr
, etc. (Note that eventually, Box<T>
may be valid
where T: Sized
, but currently it is not).
Required Methods
sourcefn ffi_default() -> Self::Value
fn ffi_default() -> Self::Value
Return an ‘empty’ value. This is what’s passed back to C in the case of an error,
so it doesn’t actually need to be “empty”, so much as “ignorable”. Note that this
is also used when an empty Option<T>
is returned.
sourcefn into_ffi_value(self) -> Self::Value
fn into_ffi_value(self) -> Self::Value
Convert ourselves into a value we can pass back to C with confidence.