首页 > 解决方案 > 在结构中存储通用闭包

问题描述

我目前正在学习 Rust。在本书的第 13 章中,有一个struct的示例。缓存器背后的想法是,仅在请求并存储该值时才对其进行评估。在示例中,缓存器具有 的输入和 的输出。因为我想让它更有用一点,所以我希望缓存器不接受任何输入并生成任何类型的值(如果你熟悉的话,基本上是来自 .NET 的类型)。Cacheri32i32Lazy<T>

我的第一个想法只是Cacher用通用注释修改给定的,如下所示:

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

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

    fn value(&mut self) -> TVal {
        match self.value { // cannot move out of `self.value.0` which is behind a mutable reference
            Some(v) => v,
            None => {
                let v = (self.calculation)();
                self.value = Some(v);
                v // use of moved value: `v`
            },
        }
    }
}

正如您在注释中看到的那样,这在我的方法中引发了一些错误value
然后我尝试了很多事情,并为该方法提出了一个可行的解决方案value。请注意,它现在返回&TVal而不是,TVal但这并没有真正困扰我。

fn value(&mut self) -> &TVal {
    if let None = self.value {
        let v = (self.calculation)();
        self.value = Some(v);
    }

    self.value.as_ref().unwrap()
}

我可以像这样创建和使用这个缓存器:

let mut expensive_val = Cacher::new(|| {
    println!("Calculating value..");
    "my result"
});

println!("Cacher was created.");
println!("The value is '{}'.", expensive_val.value());
println!("The value is still '{}'.", expensive_val.value());

// Cacher was created.
// Calculating value..
// The value is 'my result'.
// The value is still 'my result'.

这工作得很好,但我觉得有两个类型参数是多余的,所以我试图删除第一个(TCalc)。经过一番研究,我想出了这个:

struct Cacher<'a, T>
{
    calculation: &'a dyn Fn() -> T,
    value: Option<T>,
}

impl<'a, T> Cacher<'a, T>
{
    fn new(calculation: &'a dyn Fn() -> T) -> Cacher<T> {
        Cacher {
            calculation,
            value: None,
        }
    }

    fn value(&mut self) -> &T {
        if let None = self.value {
            let v = (self.calculation)();
            self.value = Some(v);
        }

        self.value.as_ref().unwrap()
    }
}

这个缓存器仍然有效,但现在我必须传入对闭包的引用而不是闭包本身。

let mut expensive_val = Cacher::new(&|| { // Note the &
    println!("Calculating value..");
    "my result"
});

我真的没有看到任何缺点,但是有没有办法在没有参考的情况下做到这一点?我的意思是使用单个类型参数,同时仍然传入闭包而不是引用。简单地尝试Fn() -> T直接存储将导致the size for values of type `(dyn std::ops::Fn() -> T + 'static)` cannot be known at compilation time.

附言。也许我说了一些错误的事情,或者你在 rust 中的做法不是这样,所以如果你能纠正我这些,请做:)

标签: genericsrust

解决方案


你是在自掘坟墓。让我们退后一步来了解为什么会发生这种情况。

您的目标是缓存一个可能很昂贵的计算,以便不必在后续的煤上重复它。但是,这意味着您的调用的返回将返回对最终结果的引用或完整值。

这个选择比它看起来对你的实现重要得多

按值返回的情况

你的Cacher结构然后变成:

struct Cacher<TCalc, TVal: Clone>
    where TCalc: Fn() -> TVal
{
    calculation: TCalc,
    value: Option<TVal>,
}

然后您的访问器变为:

fn value(&mut self) -> TVal {
    match &self.value {
        Some(r) => r.clone(),
        None => {
            self.value = Some((self.calculation)());
            self.value.clone().unwrap()
        }
    }
}

这是干净简洁的,并且依赖于您将值作为自身的克隆传递;这样做时值得做的事情是检查这个克隆实际上是一个小成本操作。

参考方法

您的方法有两个问题,这源于您相对缺乏 Rust 经验(这很好!我们都从某个地方开始):

  1. 因为您返回一个引用,并不意味着您要关闭的闭包本身就是一个引用。事实上,这样做是有害的,因为闭包已经可以关闭其他状态。让它成为参考是徒劳的
  2. 生命周期是完全没有必要的

结果,我们剩下以下内容:

struct Cacher<TCalc, TVal>
    where TCalc: Fn() -> TVal
{
    calculation: TCalc,
    value: Option<TVal>,
}
impl<TCalc, TVal> Cacher<TCalc, TVal>
    where TCalc: Fn() -> TVal {
    pub fn new(calculation: TCalc) -> Cacher<TCalc, TVal> {
        Cacher {
            calculation,
            value: None
        }
    }
    pub fn get_mut(&mut self) -> &mut TVal {
        if self.value.is_none() {
            self.value = Some((self.calculation)());
        }
        self.value.as_mut().unwrap()
    }
}

这提供了对其值的可变引用,如果它不存在,则预先创建它,从而满足要求。

这仍然存在问题,最臭名昭著的事实是,如果您想要对内部值的不可变引用,您仍然需要对容器进行可变借用,而这可以通过内部突变结构解决,但这就是另一天的故事。


推荐阅读