Do Rust builder patterns have to use redundant struct code?

巧了我就是萌 提交于 2019-12-06 06:06:31

Do Rust builder patterns have to use redundant struct code?

No. But sometimes they might. For example, consider if we wanted to have special logic (or even just complicated logic) around our constructor:

/// Width must always be greater than height!
struct HorizontalEllipse {
    width: f64,
    height: f64,
}

impl HorizontalEllipse {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.width / 2.0) * (self.height / 2.0)
    }
}

struct HorizontalEllipseBuilder {
    width: f64,
    height: f64,
}

impl HorizontalEllipseBuilder {
    fn new() -> HorizontalEllipseBuilder {
        HorizontalEllipseBuilder {
            width: 0.0,
            height: 0.0,
        }
    }

    fn width(&mut self, width: f64) -> &mut HorizontalEllipseBuilder {
        self.width = width;
        self
    }

    fn height(&mut self, height: f64) -> &mut HorizontalEllipseBuilder {
        self.height = height;
        self
    }

    fn finalize(&self) -> Result<HorizontalEllipse, String> {
        let HorizontalEllipseBuilder { height, width } = *self;
        if height >= width {
            Err("This is not horizontal".into())
        } else {
            Ok(HorizontalEllipse { width, height })
        }
    }
}

fn main() {
    let c = HorizontalEllipseBuilder::new()
        .width(1.0)
        .height(2.0)
        .finalize()
        .expect("not a valid ellipse");

    println!("area: {}", c.area());
    println!("width: {}", c.width);
    println!("height: {}", c.height);
}

Now a HorizontalEllipse knows that it is always true that width > height. We've moved that check from many potential places (each method) to one, the constructor. We then moved the constructor to a new type because it was complicated (not really, but truly complicated examples are usually... complicated).

Many builders I've seen also have "enhanced" types of the real object:

#[derive(Debug)]
struct Person {
    name: String,
}

#[derive(Debug, Default)]
struct PersonBuilder {
    name: Option<String>,
}

impl PersonBuilder {
    fn name(self, name: &str) -> Self {
        PersonBuilder { name: Some(name.into()), ..self }
    }

    fn build(self) -> Person {
        Person {
            name: self.name.unwrap_or_else(|| "Stefani Joanne Angelina Germanotta".into()),
        }
    }
}

fn main() {
    let person = PersonBuilder::default().build();
    println!("{:?}", person);

    let person = PersonBuilder::default().name("krishnab").build();
    println!("{:?}", person);
}

You don't see that in the book's example because it's trying to be simpler and not involve ownership concerns.

This seems like the sort of thing a macro might be able to do. A quick search found the derive_builder and builder_macro crates which seem to implement this functionality.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!