forked from mozilla/uniffi-rs
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Common system for remote type handling (mozilla#1865)
Remote types present an issue because we can't implement FFI traits like `Lower` and `Lift` on them directly. The workaround is to only implement the FFI trait for the current crate. If another crate wants to use that impl, we need to implement it again by forwarding to the initial implementation. Historically, UDL and proc-macros have approached this differently which has lead to a lot of extra complexity and confusion. This change aligns them on a common system: By default, we implement traits for all crates (a "blanket impl"). However, users can opt-in to only implementing it for the current crate ("a local impl`) See the doc changes for a description of the new system. This required a bunch of changes: - UDL now creates a blanket impl by default. The [Remote] attribute can be used to switch to a local impl. - Proc-macros get the `#[remote]` attribute macro, which allows users to create a local impl. - Custom types now create a blanket impl by default. The `remote` parameter can be used to create a local impl. - Added the `remote_type!` macro to which handles the case where you want to use a remote type and another crate has already created a local impl. This creates a local impl for your crate by forwarding to the other impl. Removed the `use_udl_*` macros, which were a kludge for doing the same thing. Added a motivating example where `anyhow::Error` is used as an error type. Changed the error type bound to `Display + Debug` rather than `Error`. For some reason `anyhow::Error` doesn't actually implement error. One feature that we've lost in defining callback/trait interfaces using traits defined in a remote crate. I think this is okay because I doubt anyone is using that feature.
- Loading branch information
Showing
55 changed files
with
591 additions
and
423 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
# Remote and External types | ||
|
||
Remote and external types can help solve some advanced use-cases when using UniFFI. | ||
They are grouped this section, since they're often used together. | ||
|
||
# Remote types | ||
|
||
"Remote types" refer to types defined in other crates that do not use UniFFI. | ||
This normally means types from crates that you depend on but don't control. | ||
Remote types require extra handling to use them in UniFFI APIs, because of Rust's [orphan rule](https://doc.rust-lang.org/book/traits.html#rules-for-implementing-traits). | ||
See https://github.com/mozilla/uniffi-rs/tree/main/examples/log-formatter for example code. | ||
|
||
In general, using remote types in UniFFI requires writing a type definition that mirrors the real definition found in the remote crate. | ||
|
||
## Proc-macros | ||
|
||
```rust | ||
|
||
// Type aliases can be used to give remote types nicer names when exposed in the UniFFI api. | ||
type LogLevel = log::Level; | ||
|
||
// Write a definition that mirrors the definition from the remote crate and wrap it with `[uniffi::remote(<kind>)]`. | ||
// | ||
// - UniFFI will generate the FFI scaffolding code for the item, but will not output the item itself | ||
// (since the real item is defined in the remote crate). | ||
// - `<kind>` can be any parameter that's valid for `uniffi::derive()`. | ||
#[uniffi::remote(Enum)] | ||
enum LogLevel { | ||
Error, | ||
Warn, | ||
Info, | ||
Debug, | ||
Trace, | ||
} | ||
``` | ||
|
||
## UDL | ||
|
||
Wrap the definition with `[Remote]` attribute: | ||
|
||
```idl | ||
[Remote] | ||
enum LogLevel { | ||
"Error", | ||
"Warn", | ||
"Info", | ||
"Debug", | ||
"Trace", | ||
}; | ||
``` | ||
|
||
# External Types | ||
|
||
"External types" refer to types defined in other crates that use UniFFI. | ||
This normally means types from other crates in your workspace. | ||
|
||
## Proc-macros | ||
|
||
Proc-macro-based code can use external types automatically, without any extra code. | ||
|
||
## UDL | ||
|
||
Suppose you depend on the `DemoDict` type from another UniFFIed crate in your workspace. | ||
You can reference this type by using the `[External]` attribute to wrap a typedef describing the concrete type. | ||
|
||
```idl | ||
[External] | ||
typedef dictionary One; | ||
// Now define our own dictionary which references the external type. | ||
dictionary ConsumingDict { | ||
DemoDict demo_dict; | ||
boolean another_bool; | ||
}; | ||
``` | ||
|
||
Supported values for the typedef type: | ||
|
||
* Enums: `enum` | ||
* Records: `record`, `dictionary` or `struct` | ||
* Objects: `object`, `impl` or `interface` | ||
* Traits: `trait`, `callback` or `trait_with_foreign` | ||
|
||
# Special cases for remote types | ||
|
||
There are a few cases where remote types require extra handling in addition to the rules above. | ||
|
||
## Remote + External types | ||
|
||
Types that are remote and external require a `use_remote_type!` macro call. | ||
|
||
If `crate_a` defines [IpAddr](https://doc.rust-lang.org/std/net/enum.IpAddr.html) as a remote type, then `crate_b` can use that type with the following Rust code: | ||
|
||
```rust | ||
uniffi::use_remote_type!(IpAddr, crate_a); | ||
``` | ||
|
||
## UDL | ||
|
||
UDL-users will also need to add the external type definition: | ||
|
||
```idl | ||
[External] | ||
typedef enum IpAddr; | ||
``` | ||
|
||
## Remote custom types | ||
|
||
Types that are remote and custom require using the `remote` attribute with the `custom_type!` macro. | ||
|
||
```rust | ||
|
||
uniffi::custom_type!(StructFromOtherCrate, String, { | ||
remote, | ||
lower: |s| s.to_string(), | ||
try_lift: |s| StructFromOtherCrate::try_from_string(s), | ||
}); | ||
``` | ||
|
||
## Foreign bindings | ||
|
||
The foreign bindings will also need to know how to access the external type, | ||
which varies slightly for each language: | ||
|
||
### Kotlin | ||
|
||
For Kotlin, "library mode" generation with `generate --library [path-to-cdylib]` is recommended when using external types. | ||
If you use `generate [udl-path]` then the generated code needs to know how to import | ||
the external types from the Kotlin module that corresponds to the Rust crate. | ||
By default, UniFFI assumes that the Kotlin module name matches the Rust crate name, but this can be configured in `uniffi.toml` with an entry like this: | ||
|
||
``` | ||
[bindings.kotlin.external_packages] | ||
# Map the crate names from [External={name}] into Kotlin package names | ||
rust-crate-name = "kotlin.package.name" | ||
``` | ||
|
||
### Swift | ||
|
||
For Swift, you must compile all generated `.swift` files together in a single | ||
module since the generate code expects that it can access external types | ||
without importing them. |
Oops, something went wrong.