简体   繁体   English

在结构中存储通用闭包

[英]Storing a generic closure in a struct

I'm currently in the process of learning Rust.我目前正在学习Rust。 In chapter 13 of the book in the part, there is an example of a Cacher struct .本书第 13 章中,有一个Cacher struct的示例。 The idea behind a cacher is that the value is only evaluated once it's requested and then stored.缓存器背后的想法是,仅在请求并存储该值时才对其进行评估。 In the example the cacher has an input of i32 and also an output of i32 .在示例中,缓存器的输入为i32以及 output 的i32 Since I wanted to make it a bit more useful, I wanted the cacher to not take any input and generate a value of any type (basically theLazy<T> type from .NET if you're familiar).因为我想让它更有用一点,所以我希望缓存器不接受任何输入并生成任何类型的值(基本上是.NET 中的Lazy<T>类型,如果你熟悉的话)。

My first idea was just modifying the given Cacher with generic annotations like so:我的第一个想法是使用通用注释修改给定的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`
            },
        }
    }
}

As you can see annotated by the comments, this threw some errors my way in the value method.正如您在注释中看到的那样,这在value方法中引发了一些错误。
I then tried many things and came up with a working solution for the value method.然后我尝试了很多东西,并为value方法提出了一个可行的解决方案。 Note that it now returns &TVal instead of TVal but that doesn't really bother me.请注意,它现在返回&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()
}

I can create and use this cacher like so:我可以像这样创建和使用这个缓存器:

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'.

This works just fine but I felt like having two type arguments is redundant for this so I tried to remove the first one ( TCalc ).这工作得很好,但我觉得有两种类型 arguments 是多余的,所以我试图删除第一个( TCalc )。 After some research I came up with this:经过一番研究,我想出了这个:

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

This cacher still works but now I have to pass in a reference to the closure instead of the closure itself.这个缓存器仍然有效,但现在我必须传入对闭包的引用而不是闭包本身。

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

I don't really see any disadvantage in that but is there a way to do it without a reference?我真的没有看到任何缺点,但是有没有办法在没有参考的情况下做到这一点? I mean with a single type parameter and while still passing in a closure instead of a reference.我的意思是使用单个类型参数,同时仍然传入闭包而不是引用。 Simply trying to store the Fn() -> T directly will result in the size for values of type `(dyn std::ops::Fn() -> T + 'static)` cannot be known at compilation time .简单地尝试直接存储Fn() -> T将导致the size for values of type `(dyn std::ops::Fn() -> T + 'static)` cannot be known at compilation time

Ps.附言。 Maybe I've said some things that are just wrong or not how you do it in rust so if you can correct me on those, please do:)也许我说了一些错误的事情,或者你在 rust 中的做法,所以如果你能纠正我,请做:)

You're digging yourself in a hole.你是在自掘坟墓。 Let's take a step back to understand why this is happening.让我们退后一步来了解为什么会发生这种情况。

Your goal is to cache a potentially expensive computation in order not to have to repeat it on subsequent coals.您的目标是缓存一个可能很昂贵的计算,以便不必在后续的煤上重复它。 This means, however, that the return of your call will either return a reference to the final result, or a full value.但是,这意味着您的调用的返回将返回对最终结果的引用或完整值。

This choice is much, much more important than it looks like for your implementation.这个选择比它看起来对你的实现重要得多

The case where you return by value按值返回的情况

Your Cacher struct then becomes:你的Cacher结构然后变成:

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

And your accessor then becomes:然后您的访问器变为:

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

This is clean and concise and relies on you passing the value as a clone of itself;这是干净简洁的,并且依赖于您将值作为自身的克隆传递; a worthwhile thing to do when doing this is to check that this clone actually is a small-cost operation.这样做时值得做的事情是检查这个克隆实际上是一个小成本操作。

The reference approach参考方法

Your approach had two problems, which stem from your relative lack of experience in Rust (which is fine: We've all started somewhere):您的方法有两个问题,这源于您在 Rust 方面相对缺乏经验(这很好:我们都从某个地方开始):

  1. Because you return a reference, does not mean that the closure you're closing over needs to be a reference itself.因为您返回一个引用,并不意味着您要关闭的闭包本身就是一个引用。 In fact, it is actively harmful to make it so, as the closure can already close over other state.事实上,这样做是有害的,因为关闭已经可以关闭其他 state。 Having it be a reference is futile让它成为参考是徒劳的
  2. The lifetime bound is completely unnecessary生命周期是完全没有必要的

As a result, we are left with the following:结果,我们剩下以下内容:

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

This provides a mutable reference to its value, and creates it beforehand if it does not exist, thus fulfilling the requirements.这提供了对其值的可变引用,如果它不存在,则预先创建它,从而满足要求。

There are still problems with this, most notoriously the fact that, if you wanted an immutable reference to the internal value, you'd still need a mutable borrow to the container, and that's something you can solve with an interior mutation structure, but that's a story for another day.这仍然存在问题,最臭名昭著的事实是,如果您想要对内部值的不可变引用,您仍然需要对容器进行可变借用,而这可以通过内部突变结构解决,但这就是另一天的故事。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM