简体   繁体   中英

Why does Rust's documentation for methods use a separate builder struct as an example?

I was going through the struct and method docs and was wondering why the docs use this example:

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

struct CircleBuilder {
    x: f64,
    y: f64,
    radius: f64,
}

impl CircleBuilder {
    fn new() -> CircleBuilder {
        CircleBuilder { x: 0.0, y: 0.0, radius: 1.0, }
    }

    fn x(&mut self, coordinate: f64) -> &mut CircleBuilder {
        self.x = coordinate;
        self
    }

    fn y(&mut self, coordinate: f64) -> &mut CircleBuilder {
        self.y = coordinate;
        self
    }

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

    fn finalize(&self) -> Circle {
        Circle { x: self.x, y: self.y, radius: self.radius }
    }
}

fn main() {
    let c = CircleBuilder::new()
            .x(1.0)
            .y(2.0)
            .radius(2.0)
            .finalize();

    println!("area: {}", c.area());
    println!("x: {}", c.x);
    println!("y: {}", c.y);
}

My slightly modified code is smaller and appears to do the exact same thing:

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}


impl Circle {
    fn new() -> Circle {
        Circle { x: 0.0, y: 0.0, radius: 1.0, }
    }

    fn x(&mut self, coordinate: f64) -> &mut Circle {
        self.x = coordinate;
        self
    }

    fn y(&mut self, coordinate: f64) -> &mut Circle {
        self.y = coordinate;
        self
    }

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

    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }

    fn finalize(&self) -> Circle {
        Circle { x: self.x, y: self.y, radius: self.radius }
    }
}

fn main() {
    let c = Circle::new()
            .x(1.0)
            .y(2.0)
            .radius(2.0)
            .finalize();

    println!("area: {}", c.area());
    println!("x: {}", c.x);
    println!("y: {}", c.y);
}

In general, a Circle and a CircleBuilder are not the same thing, so it makes sense to treat them as different types. In your example, once a Circle has been "finalized", there's actually nothing stopping someone from calling the builder methods ( x , y , radius ) - there's nothing enforcing it. It may also be unclear to users which methods are for building, and which are for use on a constructed object. Rust has a type system which can be used to statically avoid mistakes like this - it makes sense to use it!

In other cases, the finalize step may be less trivial - eg opening files, doing other I/O, or calculating some other private fields (which wouldn't make sense to initialise when constructing the builder).

Note that the CircleBuilder impl contains only methods that can be chained together (they return a &mut CircleBuilder ), one that initializes and one that returns a Circle .

It makes sense when someone wants to create an object "incrementally", through multiple steps, and separate those methods from eg those exposing the object's properties.

Your code is fine - it's a matter of preference. I would probably only create a new(x: f64, y: f64, radius: f64) method that would build a full Circle at once, like Circle::new(1.0, 2.0, 2.0) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM