Это правильный способ захвата строки по значению? - PullRequest
1 голос
/ 09 мая 2020

Я создаю функциональный конструктор, который настраивает объект, например:

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
  }
}

Идея состоит в том, что можно агрегировать этот построитель, а затем настроить его для такого объекта, как Person. Теперь предположим, что я хочу иметь метод построения called(), который принимает имя и сохраняет присвоенное имя в закрытии. Я реализую это следующим образом:

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
  }
}

Это правильный способ работы? Есть ли лучший способ избежать использования временной переменной и вызова clone()?

Полный рабочий пример:

#[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

1 Ответ

1 голос
/ 09 мая 2020

Вы уже это делаете, value принадлежит вашему замыканию, проблема в том, что вам требуется черта Fn. Это означает, что action (функцию) необходимо вызывать много раз. Это означает, что value необходимо клонировать, чтобы он оставался действительным внутри закрытия. Замыкание не может передать свое право собственности.

Одним из способов было бы использовать FnOnce, что позволило бы удалить clone, но это означает, что построитель можно использовать только один раз. Для этого используйте actions: Vec<Box<dyn FnOnce(&mut TSubject) -> ()>>, и action(&mut subj);.

Подробнее:

...