Exposing standard Rust traits
Rust has a number of general purpose traits which add functionality to types, such
as Debug, Display, etc. It's possible to tell UniFFI that your type implements these
traits and to generate FFI functions to expose them to consumers. Bindings may then optionally
generate special methods on the object.
The list of supported traits is hard-coded in UniFFI's internals, and at time of writing
is Debug, Display, Eq, Ord and Hash.
This is supported primarily for Interfaces - it's also supported for Records and Enums, but it has fewer use-cases there - see data-classes below.
For example, in proc-macros:
#[derive(Debug, uniffi::Object)]
#[uniffi::export(Debug)]
struct TodoList {
...
}
[Traits=(Debug)]
interface TodoList {
...
};
#[derive(Debug)]
struct TodoList {
...
}
The bindings will generate:
- Python: The object will have a
__repr__method that returns the value implemented by theDebugtrait. - Swift: Will generate a
public var debugDescription: String {..}method on the object, allowing, eg,String(reflecting: ob)to be used. - Kotlin: Will generate
override fun toString(): String {..}(although will prefer theDisplayimplementation if it exists).
Whereas Eq would generate:
- Python: The object will have
__eq__(self, other)and__ne__(self, other)methods. - Swift: Will generate
public static func == (self: .., other: ..) - Kotlin: Will generate
override fun equals(other: Any?): Booleanand have the object extendComparable<>.
etc.
External bindings may not support these, so they might be ignored.
It is your responsibility to implement the trait on your objects; UniFFI will attempt to generate a meaningful error if you do not.
Data-classes
This is supported for Records and Enums - but take care - every time one of the generated methods is called,
the entire Record or Enum will be copied by-value across the FFI.
This isn't a problem for Interfaces because exactly one pointer is copied, but data classes serialize and deserialize the entire object, which is expensive for large objects.
Further, UniFFI will automatically generate some of this support when these traits are not specified.
For example, all bindings will automatically generate simple equality check and hash functions for records which compare/hash each element in the record.
However, if you explicitly export the Eq trait, these builtin implementations will not be generated and instead will be replaced with a significantly more expensive copy and call into Rust code.
So if your Eq implementation is #[derive()]d, you are probably just providing the same semantics with a much slower implementation.
It is expected this support will prove most useful for the Debug and Display traits and used primarily for debugging,
but it's there if you need it for obscure cases where you really do need custom functionality and the cost is ok.
#[derive(uniffi::Record, Debug)]
#[uniffi::export(Debug, Eq)]
// Your `Eq` impl - there's no advantage exposing a derived `Eq`
impl Eq for TraitRecord { .. }
pub struct TraitRecord { .. }