gpt4 book ai didi

generics - 在结构中存储通用闭包

转载 作者:行者123 更新时间:2023-11-29 08:28:13 25 4
gpt4 key购买 nike

我目前正在学习 Rust。在 chapter 13 这本书 的部分,有一个 Cacher struct 的例子.缓存器背后的想法是,值只有在被请求时才会被评估,然后被存储。在示例中,缓存器的输入为 i32以及 i32 的输出.因为我想让它更有用,所以我希望缓存器不接受任何输入并生成任何类型的值(如果您熟悉的话,基本上是 Lazy<T> type from .NET)。

我的第一个想法只是修改给定的 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 中的做法,所以如果你能纠正我的错误,请纠正我:)

最佳答案

你在给自己挖坑。让我们退后一步,了解为什么会发生这种情况。

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

对于您的实现而言,这个选择远比看起来要重要

按值返回的情况

您的 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. 生命周期绑定(bind)是完全没有必要的

结果,我们留下了以下内容:

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

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

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

关于generics - 在结构中存储通用闭包,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58660854/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com