uniffi_core/lib.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! # Runtime support code for uniffi
6//!
7//! This crate provides the small amount of runtime code that is required by the generated uniffi
8//! component scaffolding in order to transfer data back and forth across the C-style FFI layer,
9//! as well as some utilities for testing the generated bindings.
10//!
11//! The key concept here is the [`FfiConverter`] trait, which is responsible for converting between
12//! a Rust type and a low-level C-style type that can be passed across the FFI:
13//!
14//! * How to [represent](FfiConverter::FfiType) values of the Rust type in the low-level C-style type
15//! system of the FFI layer.
16//! * How to ["lower"](FfiConverter::lower) values of the Rust type into an appropriate low-level
17//! FFI value.
18//! * How to ["lift"](FfiConverter::try_lift) low-level FFI values back into values of the Rust
19//! type.
20//! * How to [write](FfiConverter::write) values of the Rust type into a buffer, for cases
21//! where they are part of a compound data structure that is serialized for transfer.
22//! * How to [read](FfiConverter::try_read) values of the Rust type from buffer, for cases
23//! where they are received as part of a compound data structure that was serialized for transfer.
24//! * How to [return](FfiConverter::lower_return) values of the Rust type from scaffolding
25//! functions.
26//!
27//! This logic encapsulates the Rust-side handling of data transfer. Each foreign-language binding
28//! must also implement a matching set of data-handling rules for each data type.
29//!
30//! In addition to the core `FfiConverter` trait, we provide a handful of struct definitions useful
31//! for passing core rust types over the FFI, such as [`RustBuffer`].
32
33#![warn(rust_2018_idioms, unused_qualifications)]
34
35/// Print out tracing information for FFI calls if the `ffi-trace` feature is enabled
36#[cfg(feature = "ffi-trace")]
37#[macro_export]
38macro_rules! trace {
39 ($($tt:tt)*) => {
40 println!($($tt)*);
41 }
42}
43
44#[cfg(not(feature = "ffi-trace"))]
45#[macro_export]
46macro_rules! trace {
47 ($($tt:tt)*) => {};
48}
49
50use anyhow::bail;
51use bytes::buf::Buf;
52
53// Make Result<> public to support external impls of FfiConverter
54pub use anyhow::Result;
55
56pub mod ffi;
57mod ffi_converter_impls;
58mod ffi_converter_traits;
59pub mod metadata;
60mod oneshot;
61
62#[cfg(feature = "scaffolding-ffi-buffer-fns")]
63pub use ffi::ffiserialize::FfiBufferElement;
64pub use ffi::*;
65pub use ffi_converter_traits::{
66 ConvertError, FfiConverter, FfiConverterArc, HandleAlloc, Lift, LiftRef, LiftReturn, Lower,
67 LowerError, LowerReturn, TypeId,
68};
69pub use metadata::*;
70
71// Re-export the libs that we use in the generated code,
72// so the consumer doesn't have to depend on them directly.
73pub mod deps {
74 pub use crate::trace;
75 pub use anyhow;
76 #[cfg(feature = "tokio")]
77 pub use async_compat;
78 pub use bytes;
79 pub use static_assertions;
80}
81
82const PACKAGE_VERSION: &str = env!("CARGO_PKG_VERSION");
83
84// For the significance of this magic number 10 here, and the reason that
85// it can't be a named constant, see the `check_compatible_version` function.
86static_assertions::const_assert!(PACKAGE_VERSION.len() < 10);
87
88/// Check whether the uniffi runtime version is compatible a given uniffi_bindgen version.
89///
90/// The result of this check may be used to ensure that generated Rust scaffolding is
91/// using a compatible version of the uniffi runtime crate. It's a `const fn` so that it
92/// can be used to perform such a check at compile time.
93#[allow(clippy::len_zero)]
94pub const fn check_compatible_version(bindgen_version: &'static str) -> bool {
95 // While UniFFI is still under heavy development, we require that
96 // the runtime support crate be precisely the same version as the
97 // build-time bindgen crate.
98 //
99 // What we want to achieve here is checking two strings for equality.
100 // Unfortunately Rust doesn't yet support calling the `&str` equals method
101 // in a const context. We can hack around that by doing a byte-by-byte
102 // comparison of the underlying bytes.
103 let package_version = PACKAGE_VERSION.as_bytes();
104 let bindgen_version = bindgen_version.as_bytes();
105 // What we want to achieve here is a loop over the underlying bytes,
106 // something like:
107 // ```
108 // if package_version.len() != bindgen_version.len() {
109 // return false
110 // }
111 // for i in 0..package_version.len() {
112 // if package_version[i] != bindgen_version[i] {
113 // return false
114 // }
115 // }
116 // return true
117 // ```
118 // Unfortunately stable Rust doesn't allow `if` or `for` in const contexts,
119 // so code like the above would only work in nightly. We can hack around it by
120 // statically asserting that the string is shorter than a certain length
121 // (currently 10 bytes) and then manually unrolling that many iterations of the loop.
122 //
123 // Yes, I am aware that this is horrific, but the externally-visible
124 // behaviour is quite nice for consumers!
125 package_version.len() == bindgen_version.len()
126 && (package_version.len() == 0 || package_version[0] == bindgen_version[0])
127 && (package_version.len() <= 1 || package_version[1] == bindgen_version[1])
128 && (package_version.len() <= 2 || package_version[2] == bindgen_version[2])
129 && (package_version.len() <= 3 || package_version[3] == bindgen_version[3])
130 && (package_version.len() <= 4 || package_version[4] == bindgen_version[4])
131 && (package_version.len() <= 5 || package_version[5] == bindgen_version[5])
132 && (package_version.len() <= 6 || package_version[6] == bindgen_version[6])
133 && (package_version.len() <= 7 || package_version[7] == bindgen_version[7])
134 && (package_version.len() <= 8 || package_version[8] == bindgen_version[8])
135 && (package_version.len() <= 9 || package_version[9] == bindgen_version[9])
136 && package_version.len() < 10
137}
138
139/// Assert that the uniffi runtime version matches an expected value.
140///
141/// This is a helper hook for the generated Rust scaffolding, to produce a compile-time
142/// error if the version of `uniffi_bindgen` used to generate the scaffolding was
143/// incompatible with the version of `uniffi` being used at runtime.
144#[macro_export]
145macro_rules! assert_compatible_version {
146 ($v:expr $(,)?) => {
147 uniffi::deps::static_assertions::const_assert!(uniffi::check_compatible_version($v));
148 };
149}
150
151/// Struct to use when we want to lift/lower/serialize types inside the `uniffi` crate.
152struct UniFfiTag;
153
154/// A helper function to ensure we don't read past the end of a buffer.
155///
156/// Rust won't actually let us read past the end of a buffer, but the `Buf` trait does not support
157/// returning an explicit error in this case, and will instead panic. This is a look-before-you-leap
158/// helper function to instead return an explicit error, to help with debugging.
159pub fn check_remaining(buf: &[u8], num_bytes: usize) -> Result<()> {
160 if buf.remaining() < num_bytes {
161 bail!(
162 "not enough bytes remaining in buffer ({} < {num_bytes})",
163 buf.remaining(),
164 );
165 }
166 Ok(())
167}
168
169/// Macro to implement lowering/lifting using a `RustBuffer`
170///
171/// For complex types where it's too fiddly or too unsafe to convert them into a special-purpose
172/// C-compatible value, you can use this trait to implement `lower()` in terms of `write()` and
173/// `lift` in terms of `read()`.
174///
175/// This macro implements the boilerplate needed to define `lower`, `lift` and `FFIType`.
176#[macro_export]
177macro_rules! ffi_converter_rust_buffer_lift_and_lower {
178 ($uniffi_tag:ty) => {
179 type FfiType = $crate::RustBuffer;
180
181 fn lower(v: Self) -> $crate::RustBuffer {
182 let mut buf = ::std::vec::Vec::new();
183 <Self as $crate::FfiConverter<$uniffi_tag>>::write(v, &mut buf);
184 $crate::RustBuffer::from_vec(buf)
185 }
186
187 fn try_lift(buf: $crate::RustBuffer) -> $crate::Result<Self> {
188 let vec = buf.destroy_into_vec();
189 let mut buf = vec.as_slice();
190 let value = <Self as $crate::FfiConverter<$uniffi_tag>>::try_read(&mut buf)?;
191 match $crate::deps::bytes::Buf::remaining(&buf) {
192 0 => ::std::result::Result::Ok(value),
193 n => $crate::deps::anyhow::bail!(
194 "junk data left in buffer after lifting (count: {n})",
195 ),
196 }
197 }
198 };
199}
200
201#[cfg(test)]
202mod test {
203 use super::{FfiConverter, UniFfiTag};
204 use std::time::{Duration, SystemTime};
205
206 #[test]
207 fn timestamp_roundtrip_post_epoch() {
208 let expected = SystemTime::UNIX_EPOCH + Duration::new(100, 100);
209 let result =
210 <SystemTime as FfiConverter<UniFfiTag>>::try_lift(<SystemTime as FfiConverter<
211 UniFfiTag,
212 >>::lower(expected))
213 .expect("Failed to lift!");
214 assert_eq!(expected, result)
215 }
216
217 #[test]
218 fn timestamp_roundtrip_pre_epoch() {
219 let expected = SystemTime::UNIX_EPOCH - Duration::new(100, 100);
220 let result =
221 <SystemTime as FfiConverter<UniFfiTag>>::try_lift(<SystemTime as FfiConverter<
222 UniFfiTag,
223 >>::lower(expected))
224 .expect("Failed to lift!");
225 assert_eq!(
226 expected, result,
227 "Expected results after lowering and lifting to be equal"
228 )
229 }
230}
231
232#[cfg(test)]
233pub mod test_util {
234 use std::{error::Error, fmt};
235
236 use super::*;
237
238 #[derive(Clone, Debug, PartialEq, Eq)]
239 pub struct TestError(pub String);
240
241 // Use FfiConverter to simplify lifting TestError out of RustBuffer to check it
242 unsafe impl<UT> FfiConverter<UT> for TestError {
243 ffi_converter_rust_buffer_lift_and_lower!(UniFfiTag);
244
245 fn write(obj: TestError, buf: &mut Vec<u8>) {
246 <String as FfiConverter<UniFfiTag>>::write(obj.0, buf);
247 }
248
249 fn try_read(buf: &mut &[u8]) -> Result<TestError> {
250 <String as FfiConverter<UniFfiTag>>::try_read(buf).map(TestError)
251 }
252
253 // Use a dummy value here since we don't actually need TYPE_ID_META
254 const TYPE_ID_META: MetadataBuffer = MetadataBuffer::new();
255 }
256
257 impl fmt::Display for TestError {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 write!(f, "{}", self.0)
260 }
261 }
262
263 impl Error for TestError {}
264
265 impl<T: Into<String>> From<T> for TestError {
266 fn from(v: T) -> Self {
267 Self(v.into())
268 }
269 }
270
271 derive_ffi_traits!(blanket TestError);
272}