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?