Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use FfiConverter for metadata generation/storage #1469

Merged
merged 6 commits into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
- ABI: Implemented a new callback-interface ABI that significantly improves performance on Python and Kotlin.
- UniFFI users will automatically get the benefits of this without any code changes.
- External bindings authors will need to update their bindings code. See PR #1494 for details.
- ABI: Changed API checksum handling. This affects external bindings authors who will need to update their code to work with the new system. See PR #1469 for details.

### What's changed

- The `include_scaffolding!()` macro must now either be called from your crate root or you must have `use the_mod_that_calls_include_scaffolding::*` in your crate root. This was always the expectation, but wasn't required before. This will now start failing with errors that say `crate::UniFfiTag` does not exist.
- proc-macros now work with many more types including type aliases, type paths, etc.
- The `uniffi_types` module is no longer needed when using proc-macros.

## v0.23.0 (backend crates: v0.23.0) - (_2023-01-27_)

Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ members = [
"fixtures/keywords/kotlin",
"fixtures/keywords/rust",
"fixtures/keywords/swift",
"fixtures/metadata",
"fixtures/proc-macro",
"fixtures/reexport-scaffolding-macro",
"fixtures/regressions/enum-without-i32-helpers",
Expand All @@ -47,6 +48,7 @@ members = [
"fixtures/regressions/swift-dictionary-nesting",
"fixtures/uitests",
"fixtures/uniffi-fixture-time",
"fixtures/version-mismatch",
"fixtures/simple-fns",
"fixtures/simple-iface",
"fixtures/swift-omit-labels",
Expand Down
102 changes: 21 additions & 81 deletions docs/manual/src/internals/object_references.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,91 +56,31 @@ interface TodoList {
};
```

On the Rust side of the generated bindings, the instance constructor will create an instance of the
corresponding `TodoList` Rust struct, wrap it in an `Arc<>` and return the Arc's raw pointer to the
foreign language code:

```rust
pub extern "C" fn todolist_12ba_TodoList_new(
err: &mut uniffi::deps::ffi_support::ExternError,
) -> *const std::os::raw::c_void /* *const TodoList */ {
uniffi::deps::ffi_support::call_with_output(err, || {
let _new = TodoList::new();
let _arc = std::sync::Arc::new(_new);
<std::sync::Arc<TodoList> as uniffi::FfiConverter>::lower(_arc)
})
}
```

The UniFFI runtime implements lowering for object instances using `Arc::into_raw`:

```rust
unsafe impl<T: Sync + Send> FfiConverter for std::sync::Arc<T> {
type FfiType = *const std::os::raw::c_void;
fn lower(self) -> Self::FfiType {
std::sync::Arc::into_raw(self) as Self::FfiType
}
}
```

which does the "arc to pointer" dance for us. Note that this has "leaked" the
`Arc<>` reference out of Rusts ownership system and given it to the foreign-language code.
The foreign-language code must pass that pointer back into Rust in order to free it,
or our instance will leak.

When invoking a method on the instance, the foreign-language code passes the
raw pointer back to the Rust code, conceptually passing a "borrow" of the `Arc<>` to
the Rust scaffolding. The Rust side turns it back into a cloned `Arc<>` which
lives for the duration of the method call:

```rust
pub extern "C" fn todolist_12ba_TodoList_add_item(
ptr: *const std::os::raw::c_void,
todo: uniffi::RustBuffer,
err: &mut uniffi::deps::ffi_support::ExternError,
) -> () {
uniffi::deps::ffi_support::call_with_result(err, || -> Result<_, TodoError> {
let _retval = TodoList::add_item(
&<std::sync::Arc<TodoList> as uniffi::FfiConverter>::try_lift(ptr).unwrap(),
<String as uniffi::FfiConverter>::try_lift(todo).unwrap())?,
)
Ok(_retval)
})
}
```

The UniFFI runtime implements lifting for object instances using `Arc::from_raw`:

```rust
unsafe impl<T: Sync + Send> FfiConverter for std::sync::Arc<T> {
type FfiType = *const std::os::raw::c_void;
fn try_lift(v: Self::FfiType) -> Result<Self> {
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))
}
```

Notice that we take care to ensure the reference that is owned by the foreign-language
code remains alive.
On the Rust side of the generated bindings:
- The instance constructor will create an instance of the corresponding `TodoList` Rust struct
- The owned value is wrapped in an `Arc<>`
- The `Arc<>` is lowered into the foreign code using `Arc::into_raw` and returned as an object pointer.

This is the "arc to pointer" dance. Note that this has "leaked" the `Arc<>`
reference out of Rusts ownership system and given it to the foreign-language
code. The foreign-language code must pass that pointer back into Rust in order
to free it, or our instance will leak.

When invoking a method on the instance:
- The foreign-language code passes the raw pointer back to the Rust code, conceptually passing a "borrow" of the `Arc<>` to the Rust scaffolding.
- The Rust side calls `Arc::from_raw` to convert the pointer into an an `Arc<>`
- It wraps the `Arc` in `std::mem::ManuallyDrop<>`, which we never actually
drop. This is because the Rust side is borrowing the Arc and shouldn't
run the destructor and decrement the reference count.
- The `Arc<>` is cloned and passed to the Rust code

Finally, when the foreign-language code frees the instance, it
passes the raw pointer a special destructor function so that the Rust code can
drop that initial reference (and if that happens to be the final reference,
the Rust object will be dropped.)

```rust
pub extern "C" fn ffi_todolist_12ba_TodoList_object_free(ptr: *const std::os::raw::c_void) {
if let Err(e) = std::panic::catch_unwind(|| {
assert!(!ptr.is_null());
unsafe { std::sync::Arc::from_raw(ptr as *const TodoList) };
}) {
uniffi::deps::log::error!("ffi_todolist_12ba_TodoList_object_free panicked: {:?}", e);
}
}
```
the Rust object will be dropped.). This simply calls `Arc::from_raw`, then
lets the value drop.

Passing instances as arguments and returning them as values works similarly, except that
UniFFI does not automatically wrap/unwrap the containing `Arc`.
bendk marked this conversation as resolved.
Show resolved Hide resolved

To see this in action, use `cargo expand` to see the exact generated code.
10 changes: 0 additions & 10 deletions docs/manual/src/proc_macro/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,6 @@ User-defined types are also supported in a limited manner: records (structs with
their definition. Opaque objects (`interface` in UDL) can always be used regardless of whether they
are defined in UDL and / or via derive macro; they just need to be put inside an `Arc` as always.

User-defined types also have to be (re-)exported from a module called `uniffi_types` at the crate
root. This is required to ensure that a given type name always means the same thing across all uses
of `#[uniffi::export]` across the whole module tree.

```rust
mod uniffi_types {
pub(crate) use path::to::MyObject;
}
```

## The `uniffi::Record` derive

The `Record` derive macro exposes a `struct` with named fields over FFI. All types that are
Expand Down
2 changes: 1 addition & 1 deletion docs/uniffi-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ To expand on the previous point, here are the scenarios where `uniffi` should ge

* Increment the minor version of `uniffi`
* Once we get to `1.0` then this will change to be a major version bump.
* Update the `uniffi_bindgen::UNIFFI_CONTRACT_VERSION` string
* Update the `uniffi_bindgen::UNIFFI_CONTRACT_VERSION` value
6 changes: 0 additions & 6 deletions fixtures/futures/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,3 @@ pub async fn broken_sleep(ms: u16, fail_after: u16) {
}

include!(concat!(env!("OUT_DIR"), "/uniffi_futures.uniffi.rs"));

mod uniffi_types {
pub(crate) use super::Megaphone;
pub(crate) use super::MyError;
pub(crate) use super::MyRecord;
}
2 changes: 1 addition & 1 deletion fixtures/keywords/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl r#break {
}

#[allow(non_camel_case_types)]
trait r#continue {
pub trait r#continue {
mhammond marked this conversation as resolved.
Show resolved Hide resolved
fn r#return(&self, v: r#return) -> r#return;
fn r#continue(&self, v: Vec<Box<dyn r#continue>>) -> Option<Box<dyn r#continue>>;
fn r#break(&self, _v: Option<Arc<r#break>>) -> HashMap<u8, Arc<r#break>>;
Expand Down
14 changes: 14 additions & 0 deletions fixtures/metadata/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "uniffi_fixture_metadata"
version = "0.1.0"
edition = "2021"
license = "MPL-2.0"
publish = false

[lib]
name = "uniffi_fixture_metadata"

[dependencies]
uniffi = { path = "../../uniffi" }
uniffi_meta = { path = "../../uniffi_meta" }
uniffi_core = { path = "../../uniffi_core" }
11 changes: 11 additions & 0 deletions fixtures/metadata/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* 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/. */

/// This entire crate is just a set of tests for metadata handling. We use a separate crate
/// for testing because the metadata handling is split up between several crates, and no crate
/// on all the functionality.
#[cfg(test)]
mod tests;

pub struct UniFfiTag;
Loading