带有盒装和未盒装闭包的结构

Tom*_*řák 6 rust

我仍在内化 Rust 中的闭包以及如何最好地使用它们,所以这个问题可能有些模糊,并且可能会有一些愚蠢的子问题。我基本上是在寻找合适的习语,甚至可能改变我思考如何在 Rust 中做一些事情的方式。

存放未装箱的封口

Rust 书Cacher关于闭包章节中有一个简单的例子:

struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    calculation: T,
    value: Option<u32>,
}

impl<T> Cacher<T>
where
    T: Fn(u32) -> u32,
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation,
            value: None,
        }
    }

    fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它应该像这样使用:

let mut c = Cacher::new(|a| a);
let v1 = c.value(1);
Run Code Online (Sandbox Code Playgroud)

这是非常好的和有用的,但是如果我需要Cacher让它成为另一个结构的成员,例如(本着 Rust 书籍章节的精神),a WorkoutFactory? 由于Cacher由闭包的类型参数化,我被迫WorkoutFactory使用相同的闭包类型进行参数化。

我的理解正确吗?我猜是这样,Cacherstruct 结构取决于T计算的类型,因此 structWorkoutFactory结构必须取决于Cacher. 一方面,这感觉像是 Rust 中闭包如何工作的自然、不可避免且完全合理的结果,另一方面,这意味着

  • WorkoutFactory可以包含在另一个结构中,该结构也强制由 参数化T,它可以包含在另一个结构中,...... - 闭包类型像瘟疫一样传播。也许与其他T来自成员层次结构的深处,顶级结构的签名可能会变得非常可怕。
  • 涉及一些缓存的事实WorkoutFactory应该只是一个实现细节,也许缓存甚至在2.0版本中被添加,但类型参数在公共接口中是可见的WorkoutFactory并且需要考虑。看似实现细节现在是接口的一部分,不好:(

有没有办法在不改变 的签名的情况下解决这些问题Cacher?其他人如何应对这种情况?

存放盒装封口

如果我想摆脱类型参数,我可以Box关闭。我想出了以下代码:

struct BCacher {
    calculation: Box<Fn(u32) -> u32>,
    value: Option<u32>,
}

impl BCacher {
    fn new<T: Fn(u32) -> u32 + 'static>(calculation: T) -> BCacher {
        BCacher {
            calculation: Box::new(calculation),
            value: None,
        }
    }

    fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以完全像这样使用它Cacher

let mut c = BCacher::new(|a| a);
let v1 = c.value(1);
Run Code Online (Sandbox Code Playgroud)

...几乎:('static'注释意味着我不能这样做:

let x = 1;
let mut c = BCacher::new(|a| a + x);
Run Code Online (Sandbox Code Playgroud)

因为关闭可能会活得更久x。不幸的是,盒装版本不再可能使用非盒装版本。

此外,此版本效率较低,需要取消引用Box(正确吗?),并且 RAM 访问速度较慢。在大多数情况下,差异很可能可以忽略不计,但仍然......

我可以用生命周期注释解决第一个问题:

struct BLCacher<'a> {
    calculation: Box<Fn(u32) -> u32 + 'a>,
    value: Option<u32>,
}
Run Code Online (Sandbox Code Playgroud)

但现在我又回到Cacher了类型参数以及由此带来的所有令人不快的后果。

选择的权利

这似乎是一个不幸的情况。我有两种在结构中存储闭包的方法,每种方法都有自己的一系列问题。比方说,我愿意住在一起,并作为真棒虚构的作者Cacher箱子,我想呈现的用户的实现Cacher,在拆箱Cacher和装箱BCacher。但我不想写两次实现。什么是最好的方法 - 如果有的话 - 使用现有的Cacher实现来实现BCacher

在相关说明中(甚至可能是同一个问题),让我们假设我有一个

struct WorkoutFactory<T>
where
    T: Fn(u32) -> u32,
{
    cacher: Cacher<T>,
}
Run Code Online (Sandbox Code Playgroud)

有没有一种方法可以在GymFactory不包含类型参数的情况下实现- 出于私人目的 -WorkoutFactory带有类型参数,可能存储在Box?

概括

很长的问题,抱歉。来自 Scala,在 Rust 中使用闭包并不那么简单。我希望我已经解释了我尚未找到满意答案的挣扎。

mat*_*tiu 0

抱歉,不是完整的答案,但在这段代码中:

let x = 1;
let mut c = BCacher::new(|a| a + x);
Run Code Online (Sandbox Code Playgroud)

为什么不将其更改为:

let x = 1;
let mut c = BCacher::new(move |a| a + x);
Run Code Online (Sandbox Code Playgroud)

这样函子将吸收并拥有“x”,因此不会有任何对它的引用,这有望解决所有其他问题。