generics - 在结构中存储通用闭包
问题描述
我目前正在学习 Rust。在本书的第 13 章中,有一个struct的示例。缓存器背后的想法是,仅在请求并存储该值时才对其进行评估。在示例中,缓存器具有 的输入和 的输出。因为我想让它更有用一点,所以我希望缓存器不接受任何输入并生成任何类型的值(如果你熟悉的话,基本上是来自 .NET 的类型)。Cacher
i32
i32
Lazy<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 中的做法不是这样,所以如果你能纠正我这些,请做:)
解决方案
你是在自掘坟墓。让我们退后一步来了解为什么会发生这种情况。
您的目标是缓存一个可能很昂贵的计算,以便不必在后续的煤上重复它。但是,这意味着您的调用的返回将返回对最终结果的引用或完整值。
这个选择比它看起来对你的实现重要得多。
按值返回的情况
你的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 经验(这很好!我们都从某个地方开始):
- 因为您返回一个引用,并不意味着您要关闭的闭包本身就是一个引用。事实上,这样做是有害的,因为闭包已经可以关闭其他状态。让它成为参考是徒劳的
- 生命周期是完全没有必要的
结果,我们剩下以下内容:
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()
}
}
这提供了对其值的可变引用,如果它不存在,则预先创建它,从而满足要求。
这仍然存在问题,最臭名昭著的事实是,如果您想要对内部值的不可变引用,您仍然需要对容器进行可变借用,而这可以通过内部突变结构解决,但这就是另一天的故事。
推荐阅读
- node.js - 如何将反向代理 nginx 路由停靠到 1 个反应和 1 个快速应用程序?我的网关不好
- algorithm - 获得产生不可用化学算法的最低成本
- java - 使用 RxJava3 从房间数据库的片段中填充 Spinner 问题
- reactjs - 将文本值传递给另一个组件
- next.js - 在 NextJs SSR 中操作 DOM onClick
- java - 最短路径 Java 二维数组
- haskell - 如何为包装函数编写 Hedgehog 生成器?
- python-3.x - 按子字典的两个值对字典的字典排序
- python - 如何使用 sympy 检查矩阵是否为正方形?
- python - 直方图未显示在 matplotlib 中