1

I'm making a functional builder that configures an object such as:

struct Person
{
  name: String,
  position: String
}

The builder itself keeps a list of boxed closures to be applied to the object when it needs to be constructed:

struct FunctionalBuilder<TSubject>
  where TSubject : Default
{
  actions: Vec<Box<dyn Fn(&mut TSubject) -> ()>>
}

impl<TSubject> FunctionalBuilder<TSubject>
  where TSubject : Default
{
  fn build(self) -> TSubject
  {
    let mut subj = TSubject::default();
    for action in self.actions
    {
      (*action)(&mut subj);
    }
    subj
  }
}

The idea being that one can aggregate this builder and then customize it for an object such as Person. Now, let's say I want to have a builder method called() that takes a name and saves the assignment of the name in the closure. I implement it as follows:

impl PersonBuilder
{
  pub fn called(mut self, name: &str) -> PersonBuilder
  {
    let value = name.to_string();
    self.builder.actions.push(Box::new(move |x| {
      x.name = value.clone();
    }));
    self
  }
}

Is this the right way of doing things? Is there a better way that avoids the temporary variable and clone() call?

Complete working example:

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

struct FunctionalBuilder<TSubject>
where
    TSubject: Default,
{
    actions: Vec<Box<dyn Fn(&mut TSubject) -> ()>>,
}

impl<TSubject> FunctionalBuilder<TSubject>
where
    TSubject: Default,
{
    fn build(self) -> TSubject {
        let mut subj = TSubject::default();
        for action in self.actions {
            (*action)(&mut subj);
        }
        subj
    }

    fn new() -> FunctionalBuilder<TSubject> {
        Self {
            actions: Vec::new(),
        }
    }
}

struct PersonBuilder {
    builder: FunctionalBuilder<Person>,
}

impl PersonBuilder {
    pub fn new() -> Self {
        PersonBuilder {
            builder: FunctionalBuilder::<Person>::new(),
        }
    }

    pub fn called(mut self, name: &str) -> PersonBuilder {
        let value = name.to_string();
        self.builder.actions.push(Box::new(move |x| {
            x.name = value;
        }));
        self
    }

    pub fn build(self) -> Person {
        self.builder.build()
    }
}

pub fn main() {
    let builder = PersonBuilder::new();
    let me = builder.called("Dmitri").build();
    println!("{:?}", me);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=27eb6283836a478d5c68aa025aa4698d

Stargateur
  • 24,473
  • 8
  • 65
  • 91
Dmitri Nesteruk
  • 23,067
  • 22
  • 97
  • 166

1 Answers1

1

You already do it, value is owned by your closure, the problem is that you require the Fn trait. This mean that action (the function) need to be able to be called many times. This mean value need to be cloned to keep it valid inside the closure. The closure can't give away its ownership.

One way would be to use FnOnce, which would make it possible to remove the clone, but this means that the builder can only be used once. To do this, use actions: Vec<Box<dyn FnOnce(&mut TSubject) -> ()>>, and action(&mut subj);.

More:

Dmitri Nesteruk
  • 23,067
  • 22
  • 97
  • 166
Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • While your suggestion works, I don't understand *why* it works. `action` is a `Box`, how does it become legal to just call `action(&mut subj)` instead of through `*action`? – Dmitri Nesteruk May 10 '20 at 12:03
  • @DmitriNesteruk your original doesn't need *action either https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ffbababfeb9b683b6b9b7cfd2ef4e9d0 that just I need to get rid of it because Box need to be consume not just the FnOnce inside. That why I add the link in More, I added the blog announcement too. This is quite a advance topic you are dealing here. https://doc.rust-lang.org/1.34.0/std/boxed/trait.FnBox.html#tymethod.call_box You can see this function need to consume the box – Stargateur May 10 '20 at 12:14