summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/uniffi/src/lib.rs')
-rw-r--r--third_party/rust/uniffi/src/lib.rs670
1 files changed, 670 insertions, 0 deletions
diff --git a/third_party/rust/uniffi/src/lib.rs b/third_party/rust/uniffi/src/lib.rs
new file mode 100644
index 0000000000..df420760d5
--- /dev/null
+++ b/third_party/rust/uniffi/src/lib.rs
@@ -0,0 +1,670 @@
+/* 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/. */
+
+//! # Runtime support code for uniffi
+//!
+//! This crate provides the small amount of runtime code that is required by the generated uniffi
+//! component scaffolding in order to transfer data back and forth across the C-style FFI layer,
+//! as well as some utilities for testing the generated bindings.
+//!
+//! The key concept here is the [`FfiConverter`] trait, which is responsible for converting between
+//! a Rust type and a low-level C-style type that can be passed across the FFI:
+//!
+//! * How to [represent](FfiConverter::FfiType) values of the Rust type in the low-level C-style type
+//! system of the FFI layer.
+//! * How to ["lower"](FfiConverter::lower) values of the Rust type into an appropriate low-level
+//! FFI value.
+//! * How to ["lift"](FfiConverter::try_lift) low-level FFI values back into values of the Rust
+//! type.
+//! * How to [write](FfiConverter::write) values of the Rust type into a buffer, for cases
+//! where they are part of a compound data structure that is serialized for transfer.
+//! * How to [read](FfiConverter::try_read) values of the Rust type from buffer, for cases
+//! where they are received as part of a compound data structure that was serialized for transfer.
+//!
+//! This logic encapsulates the Rust-side handling of data transfer. Each foreign-language binding
+//! must also implement a matching set of data-handling rules for each data type.
+//!
+//! In addition to the core `FfiConverter` trait, we provide a handful of struct definitions useful
+//! for passing core rust types over the FFI, such as [`RustBuffer`].
+
+#![warn(rust_2018_idioms, unused_qualifications)]
+
+use anyhow::bail;
+use bytes::buf::{Buf, BufMut};
+use paste::paste;
+use std::{
+ collections::HashMap,
+ convert::TryFrom,
+ time::{Duration, SystemTime},
+};
+
+// Make Result<> public to support external impls of FfiConverter
+pub use anyhow::Result;
+
+pub mod ffi;
+pub use ffi::*;
+
+// It would be nice if this module was behind a cfg(test) guard, but it
+// doesn't work between crates so let's hope LLVM tree-shaking works well.
+pub mod testing;
+
+// Re-export the libs that we use in the generated code,
+// so the consumer doesn't have to depend on them directly.
+pub mod deps {
+ pub use anyhow;
+ pub use bytes;
+ pub use log;
+ pub use static_assertions;
+}
+
+pub use uniffi_macros::{export, Object, Record};
+
+mod panichook;
+
+const PACKAGE_VERSION: &str = env!("CARGO_PKG_VERSION");
+
+// For the significance of this magic number 10 here, and the reason that
+// it can't be a named constant, see the `check_compatible_version` function.
+static_assertions::const_assert!(PACKAGE_VERSION.as_bytes().len() < 10);
+
+/// Check whether the uniffi runtime version is compatible a given uniffi_bindgen version.
+///
+/// The result of this check may be used to ensure that generated Rust scaffolding is
+/// using a compatible version of the uniffi runtime crate. It's a `const fn` so that it
+/// can be used to perform such a check at compile time.
+#[allow(clippy::len_zero)]
+pub const fn check_compatible_version(bindgen_version: &'static str) -> bool {
+ // While UniFFI is still under heavy development, we require that
+ // the runtime support crate be precisely the same version as the
+ // build-time bindgen crate.
+ //
+ // What we want to achieve here is checking two strings for equality.
+ // Unfortunately Rust doesn't yet support calling the `&str` equals method
+ // in a const context. We can hack around that by doing a byte-by-byte
+ // comparison of the underlying bytes.
+ let package_version = PACKAGE_VERSION.as_bytes();
+ let bindgen_version = bindgen_version.as_bytes();
+ // What we want to achieve here is a loop over the underlying bytes,
+ // something like:
+ // ```
+ // if package_version.len() != bindgen_version.len() {
+ // return false
+ // }
+ // for i in 0..package_version.len() {
+ // if package_version[i] != bindgen_version[i] {
+ // return false
+ // }
+ // }
+ // return true
+ // ```
+ // Unfortunately stable Rust doesn't allow `if` or `for` in const contexts,
+ // so code like the above would only work in nightly. We can hack around it by
+ // statically asserting that the string is shorter than a certain length
+ // (currently 10 bytes) and then manually unrolling that many iterations of the loop.
+ //
+ // Yes, I am aware that this is horrific, but the externally-visible
+ // behaviour is quite nice for consumers!
+ package_version.len() == bindgen_version.len()
+ && (package_version.len() == 0 || package_version[0] == bindgen_version[0])
+ && (package_version.len() <= 1 || package_version[1] == bindgen_version[1])
+ && (package_version.len() <= 2 || package_version[2] == bindgen_version[2])
+ && (package_version.len() <= 3 || package_version[3] == bindgen_version[3])
+ && (package_version.len() <= 4 || package_version[4] == bindgen_version[4])
+ && (package_version.len() <= 5 || package_version[5] == bindgen_version[5])
+ && (package_version.len() <= 6 || package_version[6] == bindgen_version[6])
+ && (package_version.len() <= 7 || package_version[7] == bindgen_version[7])
+ && (package_version.len() <= 8 || package_version[8] == bindgen_version[8])
+ && (package_version.len() <= 9 || package_version[9] == bindgen_version[9])
+ && package_version.len() < 10
+}
+
+/// Assert that the uniffi runtime version matches an expected value.
+///
+/// This is a helper hook for the generated Rust scaffolding, to produce a compile-time
+/// error if the version of `uniffi_bindgen` used to generate the scaffolding was
+/// incompatible with the version of `uniffi` being used at runtime.
+#[macro_export]
+macro_rules! assert_compatible_version {
+ ($v:expr $(,)?) => {
+ uniffi::deps::static_assertions::const_assert!(uniffi::check_compatible_version($v));
+ };
+}
+
+/// Trait defining how to transfer values via the FFI layer.
+///
+/// The `FfiConverter` trait defines how to pass values of a particular type back-and-forth over
+/// the uniffi generated FFI layer, both as standalone argument or return values, and as
+/// part of serialized compound data structures.
+///
+/// (This trait is like the `IntoFfi` trait from `ffi_support`, but local to this crate
+/// so that we can add some alternative implementations for different builtin types,
+/// and so that we can add support for receiving as well as returning).
+///
+/// ## Safety
+///
+/// This is an unsafe trait (implementing it requires `unsafe impl`) because we can't guarantee
+/// that it's safe to pass your type out to foreign-language code and back again. Buggy
+/// implementations of this trait might violate some assumptions made by the generated code,
+/// or might not match with the corresponding code in the generated foreign-language bindings.
+///
+/// In general, you should not need to implement this trait by hand, and should instead rely on
+/// implementations generated from your component UDL via the `uniffi-bindgen scaffolding` command.
+
+pub unsafe trait FfiConverter: Sized {
+ /// The type used in Rust code.
+ ///
+ /// For primitive / standard types, we implement `FfiConverter` on the type itself with `RustType=Self`.
+ /// For user-defined types we create a unit struct and implement it there. This sidesteps
+ /// Rust's orphan rules (ADR-0006).
+ type RustType;
+
+ /// The low-level type used for passing values of this type over the FFI.
+ ///
+ /// This must be a C-compatible type (e.g. a numeric primitive, a `#[repr(C)]` struct) into
+ /// which values of the target rust type can be converted.
+ ///
+ /// For complex data types, we currently recommend using `RustBuffer` and serializing
+ /// the data for transfer. In theory it could be possible to build a matching
+ /// `#[repr(C)]` struct for a complex data type and pass that instead, but explicit
+ /// serialization is simpler and safer as a starting point.
+ type FfiType;
+
+ /// Lower a rust value of the target type, into an FFI value of type Self::FfiType.
+ ///
+ /// This trait method is used for sending data from rust to the foreign language code,
+ /// by (hopefully cheaply!) converting it into someting that can be passed over the FFI
+ /// and reconstructed on the other side.
+ ///
+ /// Note that this method takes an owned `Self::RustType`; this allows it to transfer ownership
+ /// in turn to the foreign language code, e.g. by boxing the value and passing a pointer.
+ fn lower(obj: Self::RustType) -> Self::FfiType;
+
+ /// Lift a rust value of the target type, from an FFI value of type Self::FfiType.
+ ///
+ /// This trait method is used for receiving data from the foreign language code in rust,
+ /// by (hopefully cheaply!) converting it from a low-level FFI value of type Self::FfiType
+ /// into a high-level rust value of the target type.
+ ///
+ /// Since we cannot statically guarantee that the foreign-language code will send valid
+ /// values of type Self::FfiType, this method is fallible.
+ fn try_lift(v: Self::FfiType) -> Result<Self::RustType>;
+
+ /// Write a rust value into a buffer, to send over the FFI in serialized form.
+ ///
+ /// This trait method can be used for sending data from rust to the foreign language code,
+ /// in cases where we're not able to use a special-purpose FFI type and must fall back to
+ /// sending serialized bytes.
+ ///
+ /// Note that this method takes an owned `Self::RustType` because it's transfering ownership
+ /// to the foreign language code via the RustBuffer.
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>);
+
+ /// Read a rust value from a buffer, received over the FFI in serialized form.
+ ///
+ /// This trait method can be used for receiving data from the foreign language code in rust,
+ /// in cases where we're not able to use a special-purpose FFI type and must fall back to
+ /// receiving serialized bytes.
+ ///
+ /// Since we cannot statically guarantee that the foreign-language code will send valid
+ /// serialized bytes for the target type, this method is fallible.
+ ///
+ /// Note the slightly unusual type here - we want a mutable reference to a slice of bytes,
+ /// because we want to be able to advance the start of the slice after reading an item
+ /// from it (but will not mutate the actual contents of the slice).
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType>;
+}
+
+/// A helper function to ensure we don't read past the end of a buffer.
+///
+/// Rust won't actually let us read past the end of a buffer, but the `Buf` trait does not support
+/// returning an explicit error in this case, and will instead panic. This is a look-before-you-leap
+/// helper function to instead return an explicit error, to help with debugging.
+pub fn check_remaining(buf: &[u8], num_bytes: usize) -> Result<()> {
+ if buf.remaining() < num_bytes {
+ bail!(
+ "not enough bytes remaining in buffer ({} < {num_bytes})",
+ buf.remaining(),
+ );
+ }
+ Ok(())
+}
+
+/// Blanket implementation of `FfiConverter` for numeric primitives.
+///
+/// Numeric primitives have a straightforward mapping into C-compatible numeric types,
+/// sice they are themselves a C-compatible numeric type!
+macro_rules! impl_via_ffi_for_num_primitive {
+ ($($T:ty,)+) => { impl_via_ffi_for_num_primitive!($($T),+); };
+ ($($T:ty),*) => {
+ $(
+ paste! {
+ unsafe impl FfiConverter for $T {
+ type RustType = Self;
+ type FfiType = Self;
+
+ fn lower(obj: Self::RustType) -> Self::FfiType {
+ obj
+ }
+
+ fn try_lift(v: Self::FfiType) -> Result<Self> {
+ Ok(v)
+ }
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ buf.[<put_ $T>](obj);
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self> {
+ check_remaining(buf, std::mem::size_of::<$T>())?;
+ Ok(buf.[<get_ $T>]())
+ }
+ }
+ }
+ )*
+ };
+}
+
+impl_via_ffi_for_num_primitive! {
+ i8, u8, i16, u16, i32, u32, i64, u64, f32, f64
+}
+
+/// Support for passing boolean values via the FFI.
+///
+/// Booleans are passed as an `i8` in order to avoid problems with handling
+/// C-compatible boolean values on JVM-based languages.
+unsafe impl FfiConverter for bool {
+ type RustType = Self;
+ type FfiType = i8;
+
+ fn lower(obj: Self::RustType) -> Self::FfiType {
+ i8::from(obj)
+ }
+
+ fn try_lift(v: Self::FfiType) -> Result<Self::RustType> {
+ Ok(match v {
+ 0 => false,
+ 1 => true,
+ _ => bail!("unexpected byte for Boolean"),
+ })
+ }
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ buf.put_i8(<bool as FfiConverter>::lower(obj));
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 1)?;
+ <bool as FfiConverter>::try_lift(buf.get_i8())
+ }
+}
+
+/// Support for passing Strings via the FFI.
+///
+/// Unlike many other implementations of `FfiConverter`, this passes a struct containing
+/// a raw pointer rather than copying the data from one side to the other. This is a
+/// safety hazard, but turns out to be pretty nice for useability. This struct
+/// *must* be a valid `RustBuffer` and it *must* contain valid utf-8 data (in other
+/// words, it *must* be a `Vec<u8>` suitable for use as an actual rust `String`).
+///
+/// When serialized in a buffer, strings are represented as a i32 byte length
+/// followed by utf8-encoded bytes. (It's a signed integer because unsigned types are
+/// currently experimental in Kotlin).
+unsafe impl FfiConverter for String {
+ type RustType = Self;
+ type FfiType = RustBuffer;
+
+ // This returns a struct with a raw pointer to the underlying bytes, so it's very
+ // important that it consume ownership of the String, which is relinquished to the
+ // foreign language code (and can be restored by it passing the pointer back).
+ fn lower(obj: Self::RustType) -> Self::FfiType {
+ RustBuffer::from_vec(obj.into_bytes())
+ }
+
+ // The argument here *must* be a uniquely-owned `RustBuffer` previously obtained
+ // from `lower` above, and hence must be the bytes of a valid rust string.
+ fn try_lift(v: Self::FfiType) -> Result<Self::RustType> {
+ let v = v.destroy_into_vec();
+ // This turns the buffer back into a `String` without copying the data
+ // and without re-checking it for validity of the utf8. If the `RustBuffer`
+ // came from a valid String then there's no point in re-checking the utf8,
+ // and if it didn't then bad things are probably going to happen regardless
+ // of whether we check for valid utf8 data or not.
+ Ok(unsafe { String::from_utf8_unchecked(v) })
+ }
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ // N.B. `len()` gives us the length in bytes, not in chars or graphemes.
+ // TODO: it would be nice not to panic here.
+ let len = i32::try_from(obj.len()).unwrap();
+ buf.put_i32(len); // We limit strings to u32::MAX bytes
+ buf.put(obj.as_bytes());
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 4)?;
+ let len = usize::try_from(buf.get_i32())?;
+ check_remaining(buf, len)?;
+ // N.B: In the general case `Buf::chunk()` may return partial data.
+ // But in the specific case of `<&[u8] as Buf>` it returns the full slice,
+ // so there is no risk of having less than `len` bytes available here.
+ let bytes = &buf.chunk()[..len];
+ let res = String::from_utf8(bytes.to_vec())?;
+ buf.advance(len);
+ Ok(res)
+ }
+}
+
+/// A helper trait to implement lowering/lifting using a `RustBuffer`
+///
+/// For complex types where it's too fiddly or too unsafe to convert them into a special-purpose
+/// C-compatible value, you can use this trait to implement `lower()` in terms of `write()` and
+/// `lift` in terms of `read()`.
+pub trait RustBufferFfiConverter: Sized {
+ type RustType;
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>);
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType>;
+}
+
+unsafe impl<T: RustBufferFfiConverter> FfiConverter for T {
+ type RustType = T::RustType;
+ type FfiType = RustBuffer;
+
+ fn lower(obj: Self::RustType) -> RustBuffer {
+ let mut buf = Vec::new();
+ <T as RustBufferFfiConverter>::write(obj, &mut buf);
+ RustBuffer::from_vec(buf)
+ }
+
+ fn try_lift(v: RustBuffer) -> Result<Self::RustType> {
+ let vec = v.destroy_into_vec();
+ let mut buf = vec.as_slice();
+ let value = T::try_read(&mut buf)?;
+ if buf.remaining() != 0 {
+ bail!("junk data left in buffer after lifting")
+ }
+ Ok(value)
+ }
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ T::write(obj, buf)
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ T::try_read(buf)
+ }
+}
+
+/// Support for passing timestamp values via the FFI.
+///
+/// Timestamps values are currently always passed by serializing to a buffer.
+///
+/// Timestamps are represented on the buffer by an i64 that indicates the
+/// direction and the magnitude in seconds of the offset from epoch, and a
+/// u32 that indicates the nanosecond portion of the offset magnitude. The
+/// nanosecond portion is expected to be between 0 and 999,999,999.
+///
+/// To build an epoch offset the absolute value of the seconds portion of the
+/// offset should be combined with the nanosecond portion. This is because
+/// the sign of the seconds portion represents the direction of the offset
+/// overall. The sign of the seconds portion can then be used to determine
+/// if the total offset should be added to or subtracted from the unix epoch.
+impl RustBufferFfiConverter for SystemTime {
+ type RustType = Self;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ let mut sign = 1;
+ let epoch_offset = obj
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap_or_else(|error| {
+ sign = -1;
+ error.duration()
+ });
+ // This panic should never happen as SystemTime typically stores seconds as i64
+ let seconds = sign
+ * i64::try_from(epoch_offset.as_secs())
+ .expect("SystemTime overflow, seconds greater than i64::MAX");
+
+ buf.put_i64(seconds);
+ buf.put_u32(epoch_offset.subsec_nanos());
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 12)?;
+ let seconds = buf.get_i64();
+ let nanos = buf.get_u32();
+ let epoch_offset = Duration::new(seconds.wrapping_abs() as u64, nanos);
+
+ if seconds >= 0 {
+ Ok(SystemTime::UNIX_EPOCH + epoch_offset)
+ } else {
+ Ok(SystemTime::UNIX_EPOCH - epoch_offset)
+ }
+ }
+}
+
+/// Support for passing duration values via the FFI.
+///
+/// Duration values are currently always passed by serializing to a buffer.
+///
+/// Durations are represented on the buffer by a u64 that indicates the
+/// magnitude in seconds, and a u32 that indicates the nanosecond portion
+/// of the magnitude. The nanosecond portion is expected to be between 0
+/// and 999,999,999.
+impl RustBufferFfiConverter for Duration {
+ type RustType = Self;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ buf.put_u64(obj.as_secs());
+ buf.put_u32(obj.subsec_nanos());
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 12)?;
+ Ok(Duration::new(buf.get_u64(), buf.get_u32()))
+ }
+}
+
+/// Support for passing optional values via the FFI.
+///
+/// Optional values are currently always passed by serializing to a buffer.
+/// We write either a zero byte for `None`, or a one byte followed by the containing
+/// item for `Some`.
+///
+/// In future we could do the same optimization as rust uses internally, where the
+/// `None` option is represented as a null pointer and the `Some` as a valid pointer,
+/// but that seems more fiddly and less safe in the short term, so it can wait.
+impl<T: FfiConverter> RustBufferFfiConverter for Option<T> {
+ type RustType = Option<T::RustType>;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ match obj {
+ None => buf.put_i8(0),
+ Some(v) => {
+ buf.put_i8(1);
+ <T as FfiConverter>::write(v, buf);
+ }
+ }
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 1)?;
+ Ok(match buf.get_i8() {
+ 0 => None,
+ 1 => Some(<T as FfiConverter>::try_read(buf)?),
+ _ => bail!("unexpected tag byte for Option"),
+ })
+ }
+}
+
+/// Support for passing vectors of values via the FFI.
+///
+/// Vectors are currently always passed by serializing to a buffer.
+/// We write a `i32` item count followed by each item in turn.
+/// (It's a signed type due to limits of the JVM).
+///
+/// Ideally we would pass `Vec<u8>` directly as a `RustBuffer` rather
+/// than serializing, and perhaps even pass other vector types using a
+/// similar struct. But that's for future work.
+impl<T: FfiConverter> RustBufferFfiConverter for Vec<T> {
+ type RustType = Vec<T::RustType>;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ // TODO: would be nice not to panic here :-/
+ let len = i32::try_from(obj.len()).unwrap();
+ buf.put_i32(len); // We limit arrays to i32::MAX items
+ for item in obj {
+ <T as FfiConverter>::write(item, buf);
+ }
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 4)?;
+ let len = usize::try_from(buf.get_i32())?;
+ let mut vec = Vec::with_capacity(len);
+ for _ in 0..len {
+ vec.push(<T as FfiConverter>::try_read(buf)?)
+ }
+ Ok(vec)
+ }
+}
+
+/// Support for associative arrays via the FFI.
+/// Note that because of webidl limitations,
+/// the key must always be of the String type.
+///
+/// HashMaps are currently always passed by serializing to a buffer.
+/// We write a `i32` entries count followed by each entry (string
+/// key followed by the value) in turn.
+/// (It's a signed type due to limits of the JVM).
+impl<K: FfiConverter, V: FfiConverter> RustBufferFfiConverter for HashMap<K, V>
+where
+ K::RustType: std::hash::Hash + Eq,
+{
+ type RustType = HashMap<K::RustType, V::RustType>;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ // TODO: would be nice not to panic here :-/
+ let len = i32::try_from(obj.len()).unwrap();
+ buf.put_i32(len); // We limit HashMaps to i32::MAX entries
+ for (key, value) in obj {
+ <K as FfiConverter>::write(key, buf);
+ <V as FfiConverter>::write(value, buf);
+ }
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 4)?;
+ let len = usize::try_from(buf.get_i32())?;
+ let mut map = HashMap::with_capacity(len);
+ for _ in 0..len {
+ let key = <K as FfiConverter>::try_read(buf)?;
+ let value = <V as FfiConverter>::try_read(buf)?;
+ map.insert(key, value);
+ }
+ Ok(map)
+ }
+}
+
+/// Support for passing reference-counted shared objects via the FFI.
+///
+/// To avoid dealing with complex lifetime semantics over the FFI, any data passed
+/// by reference must be encapsulated in an `Arc`, and must be safe to share
+/// across threads.
+unsafe impl<T: Sync + Send> FfiConverter for std::sync::Arc<T> {
+ type RustType = Self;
+ // Don't use a pointer to <T> as that requires a `pub <T>`
+ type FfiType = *const std::os::raw::c_void;
+
+ /// When lowering, we have an owned `Arc<T>` and we transfer that ownership
+ /// to the foreign-language code, "leaking" it out of Rust's ownership system
+ /// as a raw pointer. This works safely because we have unique ownership of `self`.
+ /// The foreign-language code is responsible for freeing this by calling the
+ /// `ffi_object_free` FFI function provided by the corresponding UniFFI type.
+ ///
+ /// Safety: when freeing the resulting pointer, the foreign-language code must
+ /// call the destructor function specific to the type `T`. Calling the destructor
+ /// function for other types may lead to undefined behaviour.
+ fn lower(obj: Self::RustType) -> Self::FfiType {
+ std::sync::Arc::into_raw(obj) as Self::FfiType
+ }
+
+ /// When lifting, we receive a "borrow" of the `Arc<T>` that is owned by
+ /// the foreign-language code, and make a clone of it for our own use.
+ ///
+ /// Safety: the provided value must be a pointer previously obtained by calling
+ /// the `lower()` or `write()` method of this impl.
+ fn try_lift(v: Self::FfiType) -> Result<Self::RustType> {
+ let v = v as *const T;
+ // We musn't drop the `Arc<T>` that is owned by the foreign-language code.
+ let foreign_arc = std::mem::ManuallyDrop::new(unsafe { Self::from_raw(v) });
+ // Take a clone for our own use.
+ Ok(std::sync::Arc::clone(&*foreign_arc))
+ }
+
+ /// When writing as a field of a complex structure, make a clone and transfer ownership
+ /// of it to the foreign-language code by writing its pointer into the buffer.
+ /// The foreign-language code is responsible for freeing this by calling the
+ /// `ffi_object_free` FFI function provided by the corresponding UniFFI type.
+ ///
+ /// Safety: when freeing the resulting pointer, the foreign-language code must
+ /// call the destructor function specific to the type `T`. Calling the destructor
+ /// function for other types may lead to undefined behaviour.
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ static_assertions::const_assert!(std::mem::size_of::<*const std::ffi::c_void>() <= 8);
+ buf.put_u64(Self::lower(obj) as u64);
+ }
+
+ /// When reading as a field of a complex structure, we receive a "borrow" of the `Arc<T>`
+ /// that is owned by the foreign-language code, and make a clone for our own use.
+ ///
+ /// Safety: the buffer must contain a pointer previously obtained by calling
+ /// the `lower()` or `write()` method of this impl.
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ static_assertions::const_assert!(std::mem::size_of::<*const std::ffi::c_void>() <= 8);
+ check_remaining(buf, 8)?;
+ Self::try_lift(buf.get_u64() as Self::FfiType)
+ }
+}
+
+pub fn lower_anyhow_error_or_panic<ErrConverter>(
+ err: anyhow::Error,
+ arg_name: &str,
+) -> ErrConverter::FfiType
+where
+ ErrConverter: FfiConverter,
+ ErrConverter::RustType: 'static + Sync + Send + std::fmt::Debug + std::fmt::Display,
+{
+ match err.downcast::<ErrConverter::RustType>() {
+ Ok(actual_error) => ErrConverter::lower(actual_error),
+ Err(ohno) => panic!("Failed to convert arg '{arg_name}': {ohno}"),
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn trybuild_ui_tests() {
+ let t = trybuild::TestCases::new();
+ t.compile_fail("tests/ui/*.rs");
+ }
+
+ #[test]
+ fn timestamp_roundtrip_post_epoch() {
+ let expected = SystemTime::UNIX_EPOCH + Duration::new(100, 100);
+ let result = SystemTime::try_lift(SystemTime::lower(expected)).expect("Failed to lift!");
+ assert_eq!(expected, result)
+ }
+
+ #[test]
+ fn timestamp_roundtrip_pre_epoch() {
+ let expected = SystemTime::UNIX_EPOCH - Duration::new(100, 100);
+ let result = SystemTime::try_lift(SystemTime::lower(expected)).expect("Failed to lift!");
+ assert_eq!(
+ expected, result,
+ "Expected results after lowering and lifting to be equal"
+ )
+ }
+}