简体   繁体   中英

Polymorphic builder in Rust

I want to conditionally build different objects in rust.

The following code:

trait Fruit {}

struct Tomato {
    y: f64,
}

impl Fruit for Tomato {}

trait FruitBuilder {
    type Fruit: Fruit;

    fn x(self, _: f64) -> Result<Self, ()>
    where
        Self: Sized,
    {
        Err(())
    }

    fn y(self, _: f64) -> Result<Self, ()>
    where
        Self: Sized,
    {
        Err(())
    }

    fn build(self) -> Result<Self::Fruit, ()>;
}

struct TomatoBuilder {
    y: Option<f64>,
}

impl TomatoBuilder {
    fn new() -> Self {
        Self { y: None }
    }
}

impl FruitBuilder for TomatoBuilder {
    type Fruit = Tomato;

    fn y(mut self, y: f64) -> Result<Self, ()> {
        self.y = Some(y);
        Ok(self)
    }

    fn build(self) -> Result<Self::Fruit, ()> {
        let y = match self.y {
            Some(y) => y,
            None => {
                return Err(());
            }
        };
        Ok(Self::Fruit { y: y })
    }
}

struct Apple {
    x: f64,
    y: f64,
}

impl Fruit for Apple {}

struct AppleBuilder {
    x: Option<f64>,
    y: Option<f64>,
}

impl AppleBuilder {
    fn new() -> Self {
        Self { x: None, y: None }
    }
}

impl FruitBuilder for AppleBuilder {
    type Fruit = Apple;

    fn x(mut self, x: f64) -> Result<Self, ()> {
        self.x = Some(x);
        Ok(self)
    }

    fn y(mut self, y: f64) -> Result<Self, ()> {
        self.y = Some(y);
        Ok(self)
    }

    fn build(self) -> Result<Self::Fruit, ()> {
        let x = match self.x {
            Some(x) => x,
            None => {
                return Err(());
            }
        };
        let y = match self.y {
            Some(y) => y,
            None => {
                return Err(());
            }
        };
        Ok(Self::Fruit { x: x, y: y })
    }
}

fn main() {
    let fruit_builder = match true {
        true => TomatoBuilder::new(),
        false => AppleBuilder::new(),
    };
    let _fruit = fruit_builder.build();
}

Gives the following error:

error[E0308]: `match` arms have incompatible types
   --> src/main.rs:109:18
    |
107 |       let fruit_builder = match true {
    |  _________________________-
108 | |         true => TomatoBuilder::new(),
    | |                 -------------------- this is found to be of type `TomatoBuilder`
109 | |         false => AppleBuilder::new(),
    | |                  ^^^^^^^^^^^^^^^^^^^ expected struct `TomatoBuilder`, found struct `AppleBuilder`
110 | |     };
    | |_____- `match` arms have incompatible types

For more information about this error, try `rustc --explain E0308`.

Box ing the match arms resulted in basically the same error. Adding type annotation to fruit_builder: dyn FruitBuilder<Fruit = dyn Fruit + 'static> resulted in expected trait object 'dyn FruitBuilder', found struct 'TomatoBuilder' .

How should I go about fixing this error? Should I go back to the implementations and change them, or can I just encapsulate things in the main function somehow?

There is multiple ways to solve this:

  1. Dynamic dispatch

That's what you were trying to do, but the dyn keyword need to be used behind a pointer, the easy solution would be to box them into a Box<dyn FruitBuilder<Fruit = dyn Fruit + 'static>> , but then you also need to box the resulting Fruit associated type, so you will need some heavy tricks to make this works that is not worth your mental health (I tried, and failed, at the cost of my sleep).

  1. Enums

Just make an enum that can be either one of your builders, no dynamic dispatch, no head scratch, just lot of code:

enum Builder {
    Tomato(TomatoBuilder),
    Apple(AppleBuilder)
}

enum Fruits {
    Tomato(Tomato),
    Apple(Apple)
}

impl Fruit for Fruits {}

impl FruitBuilder for Builder {
    type Fruit = Fruits;

    fn x(self, x: f64) -> Result<Self, ()>
    where
        Self: Sized,
    {
        match self {
            Builder::Apple(apple_builder) => {
                apple_builder.x(x).map(Builder::Apple)
            },
            Builder::Tomato(tomato_builder) => {
                tomato_builder.x(x).map(Builder::Tomato)
            }
        }
    }

    fn y(self, y: f64) -> Result<Self, ()>
    where
        Self: Sized,
    {
        match self {
            Builder::Apple(apple_builder) => {
                apple_builder.y(y).map(Builder::Apple)
            },
            Builder::Tomato(tomato_builder) => {
                tomato_builder.y(y).map(Builder::Tomato)
            }
        }
    }

    fn build(self) -> Result<Self::Fruit, ()> {
        match self {
            Builder::Apple(apple_builder) => {
                apple_builder.build().map(Fruits::Apple)
            },
            Builder::Tomato(tomato_builder) => {
                tomato_builder.build().map(Fruits::Tomato)
            }
        }
    }
}

fn main() {
    let fruit_builder = match true {
        true => Builder::Tomato(TomatoBuilder::new()),
        false => Builder::Apple(AppleBuilder::new()),
    };
    let _fruit = fruit_builder.build();
}

yeah it's a bit too much verbose for just a wrapper, but you get the point.

  1. Still dynamic dispatch, but much simpler

Just build your fruits in the match, then return a Box<dyn Fruit> :

fn main() {
    let _fruit: Result<Box<dyn Fruit>, ()> = match true {
        true => {
            let builder = TomatoBuilder::new();
            builder.build().map(|fruit| Box::new(fruit) as Box<dyn Fruit>)
        },
        false => {
            let builder = AppleBuilder::new();
            builder.build().map(|fruit| Box::new(fruit) as Box<dyn Fruit>)
        },
    };
}

Choose the solution that suits the more your needs, but if you are hable to make the enums work for what you are doing, then it would be better in my opinion. Yes it is more verbose, but no dynamic dispatch and no allocation, and probably way more opportunity for the compiler to optimize it.

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