chinedufn / swift-bridge

swift-bridge facilitates Rust and Swift interop.
https://chinedufn.github.io/swift-bridge
Apache License 2.0
842 stars 62 forks source link

[feature request] export default trait functions in a swift_bridge module? #281

Open jct-tympanon opened 4 months ago

jct-tympanon commented 4 months ago

Hi there,

Is there a supported way to export a default trait function via swift_bridge? Like this,

struct Example;
trait ExampleTrait {
    fn example(&self) -> &'static str {
        "hello, world"
    }
}
impl ExampleTrait for Example {}

#[swift_bridge::bridge]
mod example {
    extern "Rust" {
        type Example;
        fn example(&self) -> &str;
    }
}

"ExampleTrait" is not in scope in the generated rust module (not surprising), so you get a compile error:

no method named `example` found for reference `&Example` in the current scope
items from traits can only be used if the trait is in scope

I've been looking for a way to trick swift_bridge into producing a "use" item for the trait, but so far no luck. The workarounds I've found all require adding a dummy "impl" block for the struct, which redefines default functions so the bridge can find them. That can be automated with macros.

A more general feature, which would cover the same behavior, would be something like reusable trait bridge definitions. Something like this:

#[swift_bridge::bridge]
mod example {
   extern "Rust" {
      trait ExampleTrait;
      fn example(&self) -> &str;
   }

   extern "Rust" {
      struct ExampleA;
      impl ExampleTrait for ExampleA;
   }

   extern "Rust" {
      struct ExampleB;
      impl ExampleTrait for ExampleB;
   }
}

Do you think either of these features would be a good fit for swift_bridge (default trait functions; reusable trait definitions)? Or is there a better approach?

chinedufn commented 4 months ago

Thanks for the detailed report.

Can you describe your real-world use case? My thoughts on the right solution will depend on that.

jct-tympanon commented 4 months ago

in my case, I have two processes communicating over XPC; one written in Swift, one in Rust. the shared request and reply message types are in a Rust crate with a swift_bridge module. For transmission over XPC, I encode/decode them as JSON using serde_json.

There is a trait that looks like this, which all of the data structures implement:

pub trait JsonMessage: Sized + DeserializeOwned + Serialize {
    fn from_json(text: String) -> Result<Self> {
        Ok(serde_json::from_str(&text).map_err(MarshallingError::DeserializationError)?)
    }

    fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self).map_err(MarshallingError::SerializationError)?)
    }
}

I might have asked about serde<->Codable instead. But approaches for that seemed less obvious, and less generally useful, than trait support.

I know I could define a "MessageMarshaller" concrete type for these functions, instead of trait functions. But default trait functions are pretty common, as are blanket implementations. It would be nice if we could include such functions in the FFI.