rust 存储闭包以供重用的惯用方法?

k3fezbri  于 2022-11-12  发布在  其他
关注(0)|答案(2)|浏览(167)

在Rust中传递闭包是非常简单的,但是当存储闭包以供重用时,有多种解决方案(使用泛型函数类型、引用闭包或box、是否具有'static生存期的box?...)。
虽然我已经用不同类型的盒装类型把这个问题搞混了很多次,但我还是读了类似的问答,甚至可以猜测一下如何回答这个问题。我不知道如何处理这个问题,即使是对于简单/明显的情况,也不知道什么是一个好的起点。
为了使问题更具体,使用构建器模式存储闭包以供以后调用,使这个示例存储函数以供重用的好方法是什么?

// This example looks a bit long but its really very simple.

// * This example is most of the way to implementing the builder pattern,
//   it ust runs the code immediately instead of storing input
//   to run on `build()`.
// * Changes should only be needed where the comment `stored closures:`
//   has been written.
// * I've attempted to make this example as generic as possible,
//   but not _so_ simple that the answer wont apply to real-world use (hopefully!).

struct MyActions {
    num: i32,
    times: i32,

    // stored closures: should be stored here.
    // update_fn: Option<Fn(...)>,
    // twiddle_fn: Option<Fn(...)>,
}

impl MyActions {
    pub fn new(num: i32) -> Self {
        return MyActions {
            num: num,
            times: 1,
        }
    }

    pub fn build(self) -> i32 {
        // stored closures:
        // should run both actions if they're defined and return the result.
        return self.num;
    }

    pub fn num_times(mut self, times: i32) -> Self {
        self.times = times;
        self
    }

    pub fn num_update<F>(mut self, func: F) -> Self
        where
        F: Fn(i32) -> i32
    {
        // stored closures: run immediately for now
        for _ in 0..self.times {
            self.num = func(self.num);
        }
        self
    }

    pub fn num_twiddle<F>(mut self, func: F) -> Self
        where
        F: Fn(i32) -> i32
    {
        // stored closures: run immediately for now
        for _ in 0..self.times {
            self.num = func(self.num);
        }
        self
    }
}

// no changes needed here
fn main() {
    let act = MyActions::new(133);
    let num_other: i32 = 4;

    // builder pattern (currently executes immediately).
    let result = act
        .num_times(8)
        .num_update(|x| x * 2 + num_other)
        .num_twiddle(|x| (((x / 2) - 1) ^ (x + 1)) ^ num_other)
        .build();

    // Lets say we would want this example to work,
    // where 'times' is set after defining both functions.
    /*
    let result = act
        .num_update(|x| x * 2 + num_other)
        .num_twiddle(|x| (((x / 2) - 1) ^ (x + 1)) ^ num_other)
        .num_times(8)  // <-- order changed here
        .build();
     */

    println!("done: {}", result);
}
1wnzp6jl

1wnzp6jl1#

解决这个问题的惯用方法是将闭 Package 箱。虽然装箱闭包并在以后调用它会导致分配开销和动态调度开销,但在大多数情况下,这是可以忽略的,并且MyAction类型可以很容易地使用,并显示友好的错误消息。
或者,不同的函数可以是MyAction结构体的泛型字段,它存储闭包而不使用间接方式。这在某些情况下可以产生巨大的加速,但由于更复杂的错误消息和不能自由移动MyAction对象,这种类型的可用性下降。
如果盒装版本在分析中明显显示为慢,那么你可以移到通用版本。否则我建议留在易于使用的盒装版本。
"是否静态寿命“框
同样,为了简单起见,你可以使用'static生存期,但是MyAction结构体只能存储不借用其环境的闭包。如果你在MyAction结构体上使用生存期,并将其转发给闭包,你将能够借用你的环境,代价是泛型参数,这可能会再次使MyAction结构体更难正确使用。

2q5ifsrm

2q5ifsrm2#

为了完整起见,因为闭包所有权的一些语法并不明显,所以我尝试使用问题中的代码的一个惯用/质朴版本。

  • (如果有任何问题,请直接评论或更新)。*
  • 使用了Boxed闭包,一般来说,这似乎是首选的方法,因为它意味着多个不同的闭包类型可以存储在同一个结构中。
  • 不使用'static,这允许调用者在他们的环境中使用变量(参见问题中的num_other用法),而是在struct上使用生存期。

使用继承容器结构生存期的装箱闭包的工作示例:

use std::boxed::Box;

struct MyActions<'a> {
    num: i32,
    times: i32,

    update_fn:  Option<Box<dyn Fn(i32) -> i32 + 'a>>,
    twiddle_fn: Option<Box<dyn Fn(i32) -> i32 + 'a>>,
}

impl <'a> MyActions<'a> {
    pub fn new(num: i32) -> Self {
        return MyActions {
            num: num,
            times: 1,
            update_fn: None,
            twiddle_fn: None,
        }
    }

    pub fn build(self) -> i32 {
        let mut num = self.num;

        if let Some(update_fn) = self.update_fn {
            for _ in 0..self.times {
                num = update_fn(num);
            }
        }

        if let Some(twiddle_fn) = self.twiddle_fn {
            for _ in 0..self.times {
                num = twiddle_fn(num);
            }
        }

        return num;
    }

    pub fn num_times(mut self, times: i32) -> Self {
        self.times = times;
        self
    }

    pub fn num_update<F>(mut self, func: F) -> Self
        where
        F: 'a,
        F: Fn(i32) -> i32,
    {
        self.update_fn = Some(Box::new(func));
        self
    }

    pub fn num_twiddle<F>(mut self, func: F) -> Self
        where
        F: 'a,
        F: Fn(i32) -> i32,
    {
        self.twiddle_fn = Some(Box::new(func));
        self
    }
}

// no changes needed here
fn main() {
    let act = MyActions::new(133);
    let num_other: i32 = 4;

    // builder pattern (currently executes immediately).
    let result = act
        .num_times(8)
        .num_update(|x| x * 2 + num_other)
        .num_twiddle(|x| (((x / 2) - 1) ^ (x + 1)) ^ num_other)
        .build();

    println!("done: {}", result);

    // Lets say we would want this example to work,
    // where 'times' is set after defining both functions.
    let act = MyActions::new(133);
    let result = act
        .num_update(|x| x * 2 + num_other)
        .num_twiddle(|x| (((x / 2) - 1) ^ (x + 1)) ^ num_other)
        .num_times(8)  // <-- order changed here
        .build();

    println!("done: {}", result);
}

相关问题