Skip to content

Commit c590903

Browse files
committed
Use handles for better trait interfaces passing
Check if the trait interface handle originates from the other side of the FFI. If it does, clone the handle, rather than returning a handle to the wrapped object. Before, each time the trait object was passed across the FFI, we wrapped it another time On the foreign side, this can be accomplished by a type check. On the Rust side, this requires an extra, `#[doc(hidden)]` method on the trait. This means that UDL-defined trait interfaces need to be wrapped with an attribute macro that inserts that method. Reworked the changelog so that breaking changes for external bindings is its own section. Removed the reexport-scaffolding-macro fixture which often requires changes when the FFI changes. I don't think it's giving us enough value at this point to justify continuing to update it.
1 parent 59ce1fc commit c590903

38 files changed

+290
-358
lines changed

CHANGELOG.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,22 @@
1616

1717
### What's changed?
1818

19+
- Trait interfaces defined in UDL need to be wrapped with `#[uniffi::trait_interface]`.
20+
- Trait interfaces performance has been improved. If a trait interface handle is passed across the
21+
FFI multiple times, UniFFI will only wrap the object once rather than each time it's passed.
22+
23+
### Breaking changes for external bindings:
24+
1925
- The `rust_future_continuation_callback_set` FFI function was removed. `rust_future_poll` now
20-
inputs the callback pointer. External bindings authors will need to update their code.
21-
- The object handle FFI has changed. External bindings generators will need to update their code to
22-
use the new handle system:
23-
* A single `FfiType::Handle` is used for all object handles.
24-
* `FfiType::Handle` is always a 64-bit int.
25-
* Foreign handles must always set the lowest bit of that int.
26+
inputs the callback pointer.
27+
- The object handle FFI has changed.
28+
* A single `FfiType::Handle` is used for all object handles.
29+
* `FfiType::Handle` is always a 64-bit int.
30+
* Foreign handles must always set the lowest bit of that int.
31+
* Handles are now cloned before they are returned:
32+
* Clone Rust handles before lowering them
33+
* If you implement trait interfaces: implement the `IDX_CALLBACK_CLONE` special method for
34+
callback interfaces so that Rust can clone your handles and consume the handle in `lift`.
2635

2736
## v0.25.0 (backend crates: v0.25.0) - (_2023-10-18_)
2837

Cargo.lock

Lines changed: 0 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ members = [
3737
"fixtures/keywords/swift",
3838
"fixtures/metadata",
3939
"fixtures/proc-macro",
40-
"fixtures/reexport-scaffolding-macro",
4140
"fixtures/regressions/enum-without-i32-helpers",
4241
"fixtures/regressions/fully-qualified-types",
4342
"fixtures/regressions/kotlin-experimental-unsigned-types",

docs/manual/src/foreign_traits.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ a [compatible error type](./udl/errors.md) - see below for more on error handlin
2222
For example:
2323

2424
```rust,no_run
25+
#[uniffi::trait_interface]
2526
pub trait Keychain: Send + Sync + Debug {
2627
fn get(&self, key: String) -> Result<Option<String>, KeyChainError>;
2728
fn put(&self, key: String, value: String) -> Result<(), KeyChainError>;

docs/manual/src/proc_macro/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ impl MyObject {
8484
// Corresponding UDL:
8585
// [Trait]
8686
// interface MyTrait {};
87+
//
88+
// Note: `[uniffi::trait_interface]` is not needed when the trait is exported via a proc-macro.
8789
#[uniffi::export]
8890
trait MyTrait {
8991
// ...

docs/manual/src/udl/interfaces.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,10 @@ interface Button {
8787
8888
```
8989

90-
With the following Rust implementation:
90+
The Rust implementation needs to be wrapped in `#[uniffi::trait_interface]`:
9191

9292
```rust
93+
#[uniffi::trait_interface]
9394
pub trait Button: Send + Sync {
9495
fn name(&self) -> String;
9596
}

examples/callbacks/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ impl From<uniffi::UnexpectedUniFFICallbackError> for TelephoneError {
2121
}
2222

2323
// SIM cards.
24+
#[uniffi::trait_interface]
2425
pub trait SimCard: Send + Sync {
2526
fn name(&self) -> String;
2627
}

examples/traits/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ fn press(button: Arc<dyn Button>) -> Arc<dyn Button> {
1313
button
1414
}
1515

16+
#[uniffi::trait_interface]
1617
pub trait Button: Send + Sync {
1718
fn name(&self) -> String;
1819
}

fixtures/coverall/src/traits.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub fn get_traits() -> Vec<Arc<dyn NodeTrait>> {
1010
vec![Arc::new(Trait1::default()), Arc::new(Trait2::default())]
1111
}
1212

13+
#[uniffi::trait_interface]
1314
pub trait NodeTrait: Send + Sync + std::fmt::Debug {
1415
fn name(&self) -> String;
1516

@@ -35,6 +36,7 @@ pub fn ancestor_names(node: Arc<dyn NodeTrait>) -> Vec<String> {
3536
/// Test trait
3637
///
3738
/// The goal here is to test all possible arg, return, and error types.
39+
#[uniffi::trait_interface]
3840
pub trait Getters: Send + Sync {
3941
fn get_bool(&self, v: bool, arg2: bool) -> bool;
4042
fn get_string(&self, v: String, arg2: bool) -> Result<String, CoverallError>;

fixtures/coverall/tests/bindings/test_coverall.kts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -348,12 +348,10 @@ getTraits().let { traits ->
348348
assert(traits[1].name() == "node-2")
349349
assert(traits[1].strongCount() == 2UL)
350350

351-
// Note: this doesn't increase the Rust strong count, since we wrap the Rust impl with a
352-
// Swift impl before passing it to `setParent()`
353351
traits[0].setParent(traits[1])
354352
assert(ancestorNames(traits[0]) == listOf("node-2"))
355353
assert(ancestorNames(traits[1]).isEmpty())
356-
assert(traits[1].strongCount() == 2UL)
354+
assert(traits[1].strongCount() == 3UL)
357355
assert(traits[0].getParent()!!.name() == "node-2")
358356

359357
val ktNode = KotlinNode()
@@ -362,6 +360,10 @@ getTraits().let { traits ->
362360
assert(ancestorNames(traits[1]) == listOf("node-kt"))
363361
assert(ancestorNames(ktNode) == listOf<String>())
364362

363+
// If we get the node back from Rust, we should get the original object, not an object that's
364+
// been wrapped again.
365+
assert(traits[1].getParent() === ktNode)
366+
365367
traits[1].setParent(null)
366368
ktNode.setParent(traits[0])
367369
assert(ancestorNames(ktNode) == listOf("node-1", "node-2"))

0 commit comments

Comments
 (0)