diff --git a/CHANGELOG.md b/CHANGELOG.md index 4efd98d343..7557a0ae9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,16 @@ No consumers of any languages are impacted, only the maintainers of these language bindings. ([#2066](https://github.com/mozilla/uniffi-rs/issues/2066)) +### ⚠️ Breaking Changes ⚠️ + +- Handling of remote/custom/external types has changed significantly: + - UDL users need to add the `[Remote]` attribute for remote types + - The `UniffiCustomTypeConverter` trait is no longer used. Instead, use the `custom_type!` macro. + - The `use_udl_*` macros are no longer needed and have been removed. + - To share remote type implementations between crates, use the `use_remote_type` macro. + - The UDL syntax for external types in the UDL has been changed. + - See https://mozilla.github.io/uniffi-rs/udl/remote_custom_ext_types.html for details + - The async runtime can be specified for constructors/methods, this will override the runtime specified at the impl block level. [All changes in [[UnreleasedUniFFIVersion]]](https://github.com/mozilla/uniffi-rs/compare/v0.27.1...HEAD). diff --git a/docs/manual/src/SUMMARY.md b/docs/manual/src/SUMMARY.md index 8406e97ad2..c3cf643d9c 100644 --- a/docs/manual/src/SUMMARY.md +++ b/docs/manual/src/SUMMARY.md @@ -16,9 +16,8 @@ - [Throwing errors](./udl/errors.md) - [Interfaces/Objects](./udl/interfaces.md) - [Callback Interfaces](./udl/callback_interfaces.md) - - [External Types](./udl/ext_types.md) - - [Declaring External Types](./udl/ext_types_external.md) - - [Declaring Custom Types](./udl/custom_types.md) + - [Remote and External Types](./udl/remote_ext_types.md) + - [Custom Types](./udl/custom_types.md) - [Docstrings](./udl/docstrings.md) - [Procedural Macros: Attributes and Derives](./proc_macro/index.md) - [Futures and async support](./futures.md) diff --git a/docs/manual/src/internals/lifting_and_lowering.md b/docs/manual/src/internals/lifting_and_lowering.md index 3057b0c2a9..0d387e94ee 100644 --- a/docs/manual/src/internals/lifting_and_lowering.md +++ b/docs/manual/src/internals/lifting_and_lowering.md @@ -108,9 +108,10 @@ To work around this we do several things: - We generate a unit struct named `UniFfiTag` in the root of each UniFFIed crate. - Each crate uses the `FfiConverter` trait to lower/lift/serialize values for its scaffolding functions. -This allows us to work around the orphan rules when defining `FfiConverter` implementations. - - UniFFI consumer crates can implement lifting/lowering/serializing types for their own scaffolding functions, for example `impl FfiConverter for serde_json::Value`. This is allowed since `UniFfiTag` is a local type. +This allows us to work around the orphan rules when defining ffi trait implementations. - The `uniffi` crate can implement lifting/lowering/serializing types for all scaffolding functions using a generic impl, for example `impl FfiConverter for u8`. "UT" is short for "UniFFI Tag" - - We don't currently use this, but crates can also implement lifting/lowering/serializing their local types for all scaffolding functions using a similar generic impl (`impl FfiConverter for MyLocalType`). + - UniFFI consumer crates usually implement lifting/lowering/serializing types the same way. + - However, for remote types, they must only implement ffi traits for their local tag, for example `impl FfiConverter for serde_json::Value`. This is valid since `UniFfiTag` is a local type. + - If other crates also want to use the same remote type implementation, the need to implement the ffi traits for their local tag as well. This is what the `use_remote_type!` macro does. For more details on the specifics of the "orphan rule" and why these are legal implementations, see the [Rust Chalk Book](https://rust-lang.github.io/chalk/book/clauses/coherence.html#the-orphan-rules-in-rustc) diff --git a/docs/manual/src/kotlin/configuration.md b/docs/manual/src/kotlin/configuration.md index 19ae2a745b..69bc1889be 100644 --- a/docs/manual/src/kotlin/configuration.md +++ b/docs/manual/src/kotlin/configuration.md @@ -10,7 +10,7 @@ The generated Kotlin modules can be configured using a `uniffi.toml` configurati | `cdylib_name` | `uniffi_{namespace}`[^1] | The name of the compiled Rust library containing the FFI implementation (not needed when using `generate --library`). | | `generate_immutable_records` | `false` | Whether to generate records with immutable fields (`val` instead of `var`). | | `custom_types` | | A map which controls how custom types are exposed to Kotlin. See the [custom types section of the manual](../udl/custom_types.md#custom-types-in-the-bindings-code)| -| `external_packages` | | A map of packages to be used for the specified external crates. The key is the Rust crate name, the value is the Kotlin package which will be used referring to types in that crate. See the [external types section of the manual](../udl/ext_types_external.md#kotlin) +| `external_packages` | | A map of packages to be used for the specified external crates. The key is the Rust crate name, the value is the Kotlin package which will be used referring to types in that crate. See the [external types section of the manual](../udl/remote_custom_ext_types.md#kotlin) | `android` | `false` | Used to toggle on Android specific optimizations | `android_cleaner` | `android` | Use the [`android.system.SystemCleaner`](https://developer.android.com/reference/android/system/SystemCleaner) instead of [`java.lang.ref.Cleaner`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ref/Cleaner.html). Fallback in both instances is the one shipped with JNA. diff --git a/docs/manual/src/proc_macro/index.md b/docs/manual/src/proc_macro/index.md index 84d19b4d4e..1e4541906b 100644 --- a/docs/manual/src/proc_macro/index.md +++ b/docs/manual/src/proc_macro/index.md @@ -465,32 +465,7 @@ pub trait Person { // } ``` -## Types from dependent crates +## Mixing UDL and proc-macros -When using proc-macros, you can use types from dependent crates in your exported library, as long as -the dependent crate annotates the type with one of the UniFFI derives. However, there are a couple -exceptions: - -### Types from UDL-based dependent crates - -If the dependent crate uses a UDL file to define their types, then you must invoke one of the -`uniffi::use_udl_*!` macros, for example: - -```rust -uniffi::use_udl_record!(dependent_crate, RecordType); -uniffi::use_udl_enum!(dependent_crate, EnumType); -uniffi::use_udl_error!(dependent_crate, ErrorType); -uniffi::use_udl_object!(dependent_crate, ObjectType); -``` - -### Non-UniFFI types from dependent crates - -If the dependent crate doesn't define the type in a UDL file or use one of the UniFFI derive macros, -then it's currently not possible to use them in an proc-macro exported interface. However, we hope -to fix this limitation soon. - -## Other limitations - -In addition to the per-item limitations of the macros presented above, there is also currently a -global restriction: You can only use the proc-macros inside a crate whose name is the same as the -namespace in your UDL file. This restriction will be lifted in the future. +If you use both UDL and proc-macro generation, then your crate name must match the namespace in your +UDL file. This restriction will be lifted in the future. diff --git a/docs/manual/src/udl/custom_types.md b/docs/manual/src/udl/custom_types.md index 57e93936ad..5ec2575d9e 100644 --- a/docs/manual/src/udl/custom_types.md +++ b/docs/manual/src/udl/custom_types.md @@ -1,72 +1,95 @@ # Custom types -Custom types allow you to extend the UniFFI type system to support types from your Rust crate or 3rd -party libraries. This works by converting to and from some other UniFFI type to move data across the -FFI. You must provide a `UniffiCustomTypeConverter` implementation to convert the types. +Custom types allow you to extend the UniFFI type system by converting to and from some other UniFFI +type to move data across the FFI. ## Custom types in the scaffolding code -Consider the following trivial Rust abstraction for a "handle" which wraps an integer: +### custom_type! + +Use the `custom_type!` macro to define a new custom type. ```rust -pub struct Handle(i64); + +// Some complex struct that can be serialized/deserialized to a string. +use some_mod::SerializableStruct; + +/// `SerializableStruct` objects will be passed across the FFI the same way `String` values are. +uniffi::custom_type!(SerializableStruct, String); ``` -In this trivial example, the simplest way to expose this is with a macro. +By default: + + - Values will be converted using `>` before being lowered + - Values will be converted using `>` after being lifted. + - The `TryInto::Error` type can be anything that implements `Into` + - `>` will also work, since there is a blanket impl in the core libary. +### custom_type! with manual conversions + +You can also manually specify the conversions by passing an extra param to the macro: + +```rust +uniffi::custom_type!(SerializableStruct, String, { + from_custom: |s| s.serialize(), + try_into_custom: |s| s.deserialize(), +}); ``` + +### custom_newtype! + +The `custom_newtype!` can trivially handle newtypes that wrap a UniFFI type. + +```rust +/// handle which wraps an integer +pub struct Handle(i64); + +/// `Handle` objects will be passed across the FFI the same way `i64` values are. uniffi::custom_newtype!(Handle, i64); ``` -Or you can define this in UDL via a `typedef` with a `Custom` attribute, -defining the builtin type that it's based on. +### UDL + +Define custom types in UDL via a `typedef` with a `Custom` attribute, specifying the UniFFI type +followed by the custom type. ```idl [Custom] typedef i64 Handle; ``` -For this to work, your Rust code must also implement a special trait named -`UniffiCustomTypeConverter`. - -An implementation is provided if you used the `uniffi::custom_newtype!()` macro. -But if you use UDL or otherwise need to implement your own: +**note**: UDL users still need to call the `custom_type!` or `custom_newtype!` macro in their Rust +code. -This trait is generated by UniFFI and can be found in the generated -Rust scaffolding - it is defined as: +## User-defined types -```Rust -trait UniffiCustomTypeConverter { - type Builtin; +All examples in this section convert the custom type to a builtin type. +It's also possible to convert them to a user-defined type (Record, Enum, interface, etc.). +For example you might want to convert `log::Record` class into a UniFFI record: - fn into_custom(val: Self::Builtin) -> uniffi::Result - where - Self: Sized; - fn from_custom(obj: Self) -> Self::Builtin; -} -``` +```rust -where `Builtin` is the Rust type corresponding to the UniFFI builtin-type - `i64` in the example above. Thus, the trait -implementation for `Handle` would look something like: +pub type LogRecord = log::Record; -```rust -impl UniffiCustomTypeConverter for Handle { - type Builtin = i64; +#[derive(uniffi::Record)] +pub type LogRecordData { + level: LogLevel, + message: String, +} - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(Handle(val)) +uniffi::custom_type!(LogRecord, LogRecordData, { + from_custom: |r| LogRecordData { + level: r.level(), + message: r.to_string(), } + try_into_custom: |r| LogRecord::builder() + .level(r.level) + .args(format_args!("{}", r.message)) + .build() +}); - fn from_custom(obj: Self) -> Self::Builtin { - obj.0 - } -} ``` -Because `UniffiCustomTypeConverter` is defined in each crate, this means you can use custom types even -if they are not defined in your crate - see the 'custom_types' example which demonstrates -`url::Url` as a custom type. - ## Error handling during conversion You might have noticed that the `into_custom` function returns a `uniffi::Result` (which is an @@ -168,12 +191,32 @@ Here's how the configuration works in `uniffi.toml`. * `from_custom`: Expression to convert the custom type to the UDL type. `{}` will be replaced with the value of the custom type. * `imports` (Optional) list of modules to import for your `into_custom`/`from_custom` functions. -## Using Custom Types from other crates +## Using custom types from other crates -To use the `Handle` example above from another crate, these other crates just refer to the type -as a regular `External` type - for example, another crate might use `udl` such as: +To use custom types from other crates, use a typedef wrapped with the `[External]` attribute. +For example, if another crate wanted to use the examples above: ```idl [External="crate_defining_handle_name"] -typedef extern Handle; +typedef i64 Handle; + +[External="crate_defining_log_record_name"] +typedef dictionary LogRecord; ``` + +## Remote custom types + +Custom types that convert [Remote types](./remote_ext_types.md#remote-types) defined in other crates require special handling. + +Specify `remote` param in the `custom_type!` macro: + +```rust + +uniffi::custom_type!(SerializableStructFromOtherCrate, String, { + remote, + from_custom: |s| s.serialize(), + try_into_custom: |s| s.deserialize(), +}); +``` + +To share the custom type implementation for the remote type with other crates, use the [remote_type! macro](./remote_ext_types.md#external+remote-types). diff --git a/docs/manual/src/udl/ext_types.md b/docs/manual/src/udl/ext_types.md deleted file mode 100644 index 4ad4139c5e..0000000000 --- a/docs/manual/src/udl/ext_types.md +++ /dev/null @@ -1,42 +0,0 @@ -# External types - -External types are types implemented by UniFFI but outside of this UDL file. - -They are similar to, but different from [custom types](./custom_types.md) which wrap UniFFI primitive types. - -But like custom types, external types are all declared using a `typedef` with attributes -giving more detail. - -## Types in another crate - -[There's a whole page about that!](./ext_types_external.md) - -## Types from procmacros in this crate. - -If your crate has types defined via `#[uniffi::export]` etc you can make them available -to the UDL file in your own crate via a `typedef` with a `[Rust=]` attribute. Eg, your Rust -might have: - -```rust -#[derive(uniffi::Record)] -pub struct One { - inner: i32, -} -``` -you can use it in your UDL: - -```idl -[Rust="record"] -typedef extern One; - -namespace app { - // use the procmacro type. - One get_one(One? one); -} - -``` - -Supported values: -* "enum", "trait", "callback", "trait_with_foreign" -* For records, either "record" or "dictionary" -* For objects, either "object" or "interface" diff --git a/docs/manual/src/udl/ext_types_external.md b/docs/manual/src/udl/ext_types_external.md deleted file mode 100644 index 7484323a68..0000000000 --- a/docs/manual/src/udl/ext_types_external.md +++ /dev/null @@ -1,90 +0,0 @@ -# Declaring External Types - -It is possible to use types defined by UniFFI in an external crate. For example, let's assume -that you have an existing crate named `demo_crate` with the following UDL: - -```idl -dictionary DemoDict { - string string_val; - boolean bool_val; -}; -``` - -Inside another crate, `consuming_crate`, you'd like to use this dictionary. -Inside `consuming_crate`'s UDL file you can reference `DemoDict` by using a -`typedef` with an `External` attribute, as shown below. - -```idl -[External="demo_crate"] -typedef extern DemoDict; - -// Now define our own dictionary which references the imported type. -dictionary ConsumingDict { - DemoDict demo_dict; - boolean another_bool; -}; - -``` - -Inside `consuming_crate`'s Rust code you must `use` that struct as normal - for example, -`consuming_crate`'s `lib.rs` might look like: - -```rust -use demo_crate::DemoDict; - -pub struct ConsumingDict { - demo_dict: DemoDict, - another_bool: bool, -} - -uniffi::include_scaffolding!("consuming_crate"); -``` - -Your `Cargo.toml` must reference the external crate as normal. - -The `External` attribute can be specified on dictionaries, enums, errors. - -## External interface and trait types - -If the external type is an [Interface](./interfaces.md), then use the `[ExternalInterface]` attribute instead of `[External]`: - -```idl -[ExternalInterface="demo_crate"] -typedef extern DemoInterface; -``` - -similarly for traits: use `[ExternalTrait]`. - -## External procmacro types - -The above examples assume the external types were defined via UDL. -If they were defined by procmacros, you need different attribute names: - -- if `DemoDict` is implemented by a procmacro in `demo_crate`, you'd use `[ExternalExport=...]` -- for `DemoInterface` you'd use `[ExternalInterfaceExport=...]` - -For types defined by procmacros in _this_ crate, see the [attribute `[Rust=...]`](./ext_types.md) - -## 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. diff --git a/docs/manual/src/udl/remote_ext_types.md b/docs/manual/src/udl/remote_ext_types.md new file mode 100644 index 0000000000..d45084b0d6 --- /dev/null +++ b/docs/manual/src/udl/remote_ext_types.md @@ -0,0 +1,120 @@ +# Remote, Custom and External types + +Remote, custom, 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 + +Rust's [orphan rule](https://doc.rust-lang.org/book/traits.html#rules-for-implementing-traits) places restrictions on implementing traits for types defined in other crates. +Because of that, Remote types require extra handling to use them in UniFFI APIs. + +- See https://github.com/mozilla/uniffi-rs/tree/main/examples/log-formatter for example code. + +## Proc-macros + +```rust + +// Type aliases can be used to give remote types nicer names when exposed in the UniFFI api. +type LogLevel = log::Level; + +// Wrap the definition of the type with `[uniffi::remote()]`. +// +// - The definiton should match the definition on the remote side. +// - 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). +// - `` 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 + +It is possible to use types defined by UniFFI in an external crate. For example, let's assume +that you have an existing crate with the following UDL: + +```idl +dictionary DemoDict { + string string_val; + boolean bool_val; +}; + +namespace demo_crate { + ... +}; +``` + +Inside another crate, `consuming_crate`, you'd like to use this dictionary. +Inside `consuming_crate`'s UDL file you can reference `DemoDict` by using the +`[External=]` attribute to wrap an empty definition. + +```idl +[External="demo_crate"] +dictionary DemoDict { } + +// Now define our own dictionary which references the imported type. +dictionary ConsumingDict { + DemoDict demo_dict; + boolean another_bool; +}; +``` + + +## External + Remote types + +If a type is both External and Remote, then it requires some special handling to work around Rust's +orphan rules. Call the `use_remote_type!` macro to handle this. `use_remote_type!` works with both +UDL and proc-macro based generation. + +```rust +uniffi::use_remote_type!(RemoteType, crate_with_the_remote_type_implementation); +``` + +## Proc-macros + +Proc-macros do not need to take any special action to use external types (other than the external + +remote) case above. + +## 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.