Builder pattern with uniffi

Trying to implement a builder pattern in Rust to be used in Swift with UniFFI but i found a sharp edge which is this one

A regular builder pattern would be like this:

pub struct MyThingBuilder {
    field1: Option<String>,
    field2: Option<u32>,
}

impl MyThingBuilder {
    pub fn new() -> Self {
        Self {
            field1: None,
            field2: None,
        }
    }

    pub fn field1(mut self, val: String) -> Self {
        self.field1 = Some(val);
        self
    }

    pub fn field2(mut self, val: u32) -> Self {
        self.field2 = Some(val);
        self
    }

    pub fn build(self) -> MyThing {
        MyThing {
            field1: self.field1.unwrap_or_default(),
            field2: self.field2.unwrap_or_default(),
        }
    }
}

But returning self each time seems to be incompatible with UniFFI. Therefor the solution i get from clankers is to write it like this:

    use std::sync::Arc;

    pub struct MyThing {
        pub field1: String,
        pub field2: u32,
    }
    
    pub struct MyThingBuilder {
        field1: Option<String>,
        field2: Option<u32>,
    }
    
    impl MyThingBuilder {
        pub fn new() -> Arc<Self> {
            Arc::new(Self {
                field1: None,
                field2: None,
            })
        }
    
        pub fn field1(self: Arc<Self>, val: String) -> Arc<Self> {
            let mut new = (*self).clone();
            new.field1 = Some(val);
            Arc::new(new)
        }
    
        pub fn field2(self: Arc<Self>, val: u32) -> Arc<Self> {
            let mut new = (*self).clone();
            new.field2 = Some(val);
            Arc::new(new)
        }
    
        pub fn build(self: Arc<Self>) -> MyThing {
            MyThing {
                field1: self.field1.clone().unwrap_or_default(),
                field2: self.field2.unwrap_or_default(),
            }
        }
    }

    impl Clone for MyThingBuilder {
        fn clone(&self) -> Self {
            Self {
                field1: self.field1.clone(),
                field2: self.field2,
            }
        }
    }

Is there a cleaner way of getting this working with UniFFI without cloning each time i return data per chaining? Am i missing something?

Arc<Self> approach seems correct and nice.

In the UniFFI Docs they talk about constructors: here

Constructors

Interfaces can have one or more constructors. They must have a constructor to be directly created from foreign bindings.

TodoList has a new() method. This can be exposed via UDL with constructor, or via proc-macros with a #[uniffi::constructor] attribute.

Along with a default constructor, an interface can have named constructors, implemented as static functions.

Constructors: * may omit or include the outer Arc<> - eg, we could have written fn new() -> Arc<Self> * can return a Result<> * can be async, although foreign language constraints means support for async primary constructors is patchy.

Also I found an example from the docs: example

#[uniffi::export]
impl MyObject {
    // Constructors need to be annotated as such.
    // The return value can be either `Self` or `Arc<Self>`
    // It is the primary constructor, so in most languages this is invoked with
    `MyObject()`.
    #[uniffi::constructor]
    fn new(argument: String) -> Arc<Self> {
        // ...
    }

    // Constructors with different names are also supported, usually invoked
    // as `MyObject.named()` (depending on the target language)
    #[uniffi::constructor]
    fn named() -> Arc<Self> {
        // ...
    }

    // All functions that are not constructors must have a `self` argument
    fn method_a(&self) {
        // ...
    }

    // Returning objects is also supported, either as `Self` or `Arc<Self>`
    fn method_b(self: Arc<Self>) {
        // ...
    }
}

Hi, one of the UniFFI maintainers here!

The Arc way should work, but it sure is less than optimal. Regardless of how, the builder pattern will require multiple calls across the ffi bridge. IMO that should be avoided if you can.
We do have a bug on file about exactly this, with some ideas how we could improve it though: How to implement builder pattern using uniffi? · Issue #2208 · mozilla/uniffi-rs · GitHub

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.