Skip to content

Commit 40df809

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 9c0f45e commit 40df809

37 files changed

+284
-357
lines changed

CHANGELOG.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,9 @@
1616

1717
### What's changed?
1818

19-
- 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.
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.
2622
- The `NoPointer` singleton was renamed to `NoHandle`
2723

2824
### What's new?
@@ -34,6 +30,16 @@
3430
- UDL files can reference types defined in procmacros in this crate - see
3531
[the external types docs](https://mozilla.github.io/uniffi-rs/udl/ext_types.html)
3632

33+
### Breaking changes for external bindings
34+
35+
- The `rust_future_continuation_callback_set` FFI function was removed. `rust_future_poll` now
36+
inputs the callback pointer. External bindings authors will need to update their code.
37+
- The object handle FFI has changed. External bindings generators will need to update their code to
38+
use the new handle system:
39+
* A single `FfiType::Handle` is used for all object handles.
40+
* `FfiType::Handle` is always a 64-bit int.
41+
* Foreign handles must always set the lowest bit of that int.
42+
3743
[All changes in [[UnreleasedUniFFIVersion]]](https://github.com/mozilla/uniffi-rs/compare/v0.25.2...HEAD).
3844

3945
## v0.25.2 (backend crates: v0.25.2) - (_2023-11-20_)

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
@@ -38,7 +38,6 @@ members = [
3838
"fixtures/keywords/swift",
3939
"fixtures/metadata",
4040
"fixtures/proc-macro",
41-
"fixtures/reexport-scaffolding-macro",
4241
"fixtures/regressions/enum-without-i32-helpers",
4342
"fixtures/regressions/fully-qualified-types",
4443
"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
@@ -371,12 +371,10 @@ getTraits().let { traits ->
371371
assert(traits[1].name() == "node-2")
372372
assert(traits[1].strongCount() == 2UL)
373373

374-
// Note: this doesn't increase the Rust strong count, since we wrap the Rust impl with a
375-
// Swift impl before passing it to `setParent()`
376374
traits[0].setParent(traits[1])
377375
assert(ancestorNames(traits[0]) == listOf("node-2"))
378376
assert(ancestorNames(traits[1]).isEmpty())
379-
assert(traits[1].strongCount() == 2UL)
377+
assert(traits[1].strongCount() == 3UL)
380378
assert(traits[0].getParent()!!.name() == "node-2")
381379

382380
val ktNode = KotlinNode()
@@ -385,6 +383,10 @@ getTraits().let { traits ->
385383
assert(ancestorNames(traits[1]) == listOf("node-kt"))
386384
assert(ancestorNames(ktNode) == listOf<String>())
387385

386+
// If we get the node back from Rust, we should get the original object, not an object that's
387+
// been wrapped again.
388+
assert(traits[1].getParent() === ktNode)
389+
388390
traits[1].setParent(null)
389391
ktNode.setParent(traits[0])
390392
assert(ancestorNames(ktNode) == listOf("node-1", "node-2"))

0 commit comments

Comments
 (0)