[英]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.这个选择比它看起来对你的实现重要得多。
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.
这样做时值得做的事情是检查这个克隆实际上是一个小成本操作。
Your approach had two problems, which stem from your relative lack of experience in Rust (which is fine: We've all started somewhere):您的方法有两个问题,这源于您在 Rust 方面相对缺乏经验(这很好:我们都从某个地方开始):
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.