0
\$\begingroup\$

I've been looking on how to use Builder pattern on rust with structs, but all examples I've seen only use primitive data.

Given the structs:

struct Bar {
    val1: i8,
    val2: i8,
    val3: i8,
}
struct Foo {
    bar1: Bar,
    bar2: Bar,
    val1: Option<i8>,
    val2: i8,
}

The builder for Bar is quite straightforward:

#[derive(Default)]
struct BarBuilder {
    val1: Option<i8>,
    val2: Option<i8>,
    val3: Option<i8>,
}
impl BarBuilder {
    pub fn val1(&mut self, val1: i8) -> &mut Self {
        self.val1 = Some(val1);
        self
    }
    pub fn val2(&mut self, val2: i8) -> &mut Self {
        self.val2 = Some(val2);
        self
    }
    pub fn val3(&mut self, val3: i8) -> &mut Self {
        self.val3 = Some(val3);
        self
    }
    pub fn build(&mut self) -> Result<Bar, String> {
        Ok(Bar {
            val1: self.val1.ok_or("val1 empty")?,
            val2: self.val2.ok_or("val2 empty")?,
            val3: self.val3.ok_or("val3 empty")?,
        })
    }
}

But, how should I implement FooBuilder?

#[derive(Default)]
struct FooBuilder {
    //1. Which would be better Option<Bar> or BarBuilder?
    bar1: Option<Bar>,
    bar2: BarBuilder,
    val1: Option<i8>,
    val2: Option<i8>,
}
impl FooBuilder {
    //2. the data in Foo is Option<i8>, what should be the parameter? i8 or Option<i8>?
    pub fn val1(&mut self, val1: i8) -> &mut Self {
        self.val1 = Some(val1);
        self
    }
    //3. Are early validations Ok? or should just be on build?
    pub fn val2(&mut self, val2: i8) -> Result<&mut Self, String> {
        if val2 % 2 == 1 {
            Err("val2 must be pair".to_string())
        } else {
            self.val2 = Some(val2);
            Ok(self)
        }
    }
    //Expects a built Bar
    pub fn bar1(&mut self, bar1: Bar) -> &mut Self {
        self.bar1 = Some(bar1);
        self
    }
    //Returns a BarBuilder to be setted
    pub fn bar2(&mut self) -> &mut BarBuilder {
        &mut self.bar2
    }
    pub fn build(&mut self) -> Result<Foo, String> {
        Ok(Foo {
            val1: self.val1,
            val2: {
                let val2 = self.val2.ok_or("val2 empty")?;
                if val2 % 2 == 1 {
                    return Err("val2 must be pair".to_string());
                }
                val2
            },
            //We need to copy bar1, so it will have to #[derive(Clone)]
            bar1: self.bar1.clone().ok_or("bar2 empty")?,
            bar2: self.bar2.build()?
        })
    }
}

main

fn main() -> Result<(), String>{
    let expected_result = Foo {
        bar1: Bar { val1: 1, val2: 2, val3: 3 },
        bar2: Bar { val1: 3, val2: 4, val3: 5 },
        val1: Some(6),
        val2: 8,
    };
    let mut foo_builder = FooBuilder::default();
    foo_builder.val1(6)
               .bar1(BarBuilder::default().val1(1)
                                          .val2(2)
                                          .val3(3)
                                          .build()?)
               //From here on is BarBuilder, there's no way to go back
               //4. Should add .parent() and a reference to it's parent builder?
               //This becomes quite cumbersome to code in Rust with Rc, Weak and RefCell for every builder... since there is no inheritance, with macros maybe?
               .bar2().val1(3)
                      .val2(4)
                      .val3(5);
    foo_builder.val2(8)?;
    
    assert_eq!(expected_result, foo_builder.build()?);
    
    Ok(())
}

The solution I like most is having Builder' inside Builder, and adding the parent:

let mut foo_builder = FooBuilder::default::<T>();
foo_builder.val1(6)
           .bar1().val1(1)
                  .val2(2)
                  .val3(3)).parent()?
           .bar2().val1(3)
                  .val2(4)
                  .val3(5).parent()?
           .val2(8)?;

Would that be a good (but cumbersome to code) approach?

New contributor
user31872722 is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
\$\endgroup\$

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.