mozilla / uniffi-rs

a multi-language bindings generator for rust
https://mozilla.github.io/uniffi-rs/
Mozilla Public License 2.0
2.68k stars 219 forks source link

Enum with associated data containing a callback interface produces incorrect Swit code. #1673

Open Zollerboy1 opened 1 year ago

Zollerboy1 commented 1 year ago

UniFFI automatically declares enums in Swift as being Equatable and Hashable. This results in a compiler error when the associated data of the enum contains a callback interface.

Example Rust code:

pub trait Foo: Send + Sync {
    fn value(&self) -> u32;
}

pub enum Bar {
    Baz { foo: Box<dyn Foo> },
}

pub fn get_foo(bar: Bar) -> u32 {
    match bar {
        Bar::Baz { foo } => foo.value(),
    }
}

Example UDL file:

namespace test_uni_ffi {
    u32 get_foo(Bar bar);
};

callback interface Foo {
    u32 value();
};

[Enum]
interface Bar {
    Baz(Foo foo);
};

Relevant part of the produced Swift code:

public protocol Foo: AnyObject {
    func value() -> UInt32
}

public enum Bar {
    case baz(foo: Foo)
}

extension Bar: Equatable, Hashable {}
// error: type 'Bar' does not conform to protocol 'Equatable'
// error: type 'Bar' does not conform to protocol 'Hashable'

public func getFoo(bar: Bar) -> UInt32 {
    // ...
}
badboy commented 1 year ago

I only gave this a very shallow look just now. We do try to avoid those implementations for types that contain other object references: https://github.com/mozilla/uniffi-rs/blob/1696344a8f1643cfa1a36a3fac201340b0b62a98/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift

Maybe we should extend that.

pierre-wehbe commented 1 month ago

+1 I have a struct

#[derive(uniffi::Object)]
pub struct Animal {
     pub name: String;
}
#[derive(uniffi::Record)]
pub struct Person {
     pub animal: Arc<Animal>
}

In swift, this generates a Person which conforms to equatable, but Animal doesn't

mhammond commented 1 month ago

In swift, this generates a Person which conforms to equatable, but Animal doesn't

That's because Swift is able to directly compare each field for the record. However, it is possible to expose some builtin Rust traits on an interface, such as Eq etc, which Swift should then support.

https://mozilla.github.io/uniffi-rs/latest/udl/interfaces.html#exposing-methods-from-standard-rust-traits

Here is an example of Rust code doing that and of the swift code testing it

(In fact with this capability, I suspect this issue should be closed?)

mhammond commented 1 month ago

(In fact with this capability, I suspect this issue should be closed?)

hrm, probably not - the original issue is about interfaces inside records, so that's probably still true. Using Eq might help interfaces in that case, but probably not traits/callbacks.

pierre-wehbe commented 1 month ago

@mhammond My current workaround is to implement Equatable/Hashable as an extension

extension Animal: Equatable, Hashable {
... // this tend to be a bit more custom for Objects
}