Rust从入门到实战系列一百四十二:使用带有泛型和 Fn trait 的闭包

回到我们的健身计划生成 app ,在示例 13-6 中的代码仍然把慢计算闭包调用了比所需更多的次数。解
决这个问题的一个方法是在全部代码中的每一个需要多个慢计算闭包结果的地方,可以将结果保存进变
量以供复用,这样就可以使用变量而不是再次调用闭包。但是这样就会有很多重复的保存结果变量的地方。
幸运的是,还有另一个可用的方案。可以创建一个存放闭包和调用闭包结果的结构体。该结构体只会在
需要结果时执行闭包,并会缓存结果值,这样余下的代码就不必再负责保存结果并可以复用该值。你可
能见过这种模式被称 memoization 或 lazy evaluation (惰性求值)。
为了让结构体存放闭包,我们需要指定闭包的类型,因为结构体定义需要知道其每一个字段的类型。每
一个闭包实例有其自己独有的匿名类型:也就是说,即便两个闭包有着相同的签名,他们的类型仍然可
以被认为是不同。为了定义使用闭包的结构体、枚举或函数参数,需要像第十章讨论的那样使用泛型和
trait bound。
Fn 系列 trait 由标准库提供。所有的闭包都实现了 trait Fn、FnMut 或 FnOnce 中的一个。在 ” 闭包会捕
获其环境” 部分我们会讨论这些 trait 的区别;在这个例子中可以使用 Fn trait。
为了满足 Fn trait bound 我们增加了代表闭包所必须的参数和返回值类型的类型。在这个例子中,闭包
有一个 u32 的参数并返回一个 u32,这样所指定的 trait bound 就是 Fn(u32) −> u32。
了存放了闭包和一个 Option 结果值的 Cacher 结构体的定义:
struct Cacher
where
T: Fn(u32) -> u32,
{
calculation: T,
value: Option,
}

fn main() {}

定义一个 Cacher 结构体来在 calculation 中存放闭包并在 value 中存放 Option 值
结构体 Cacher 有一个泛型 T 的字段 calculation。T 的 trait bound 指定了 T 是一个使用 Fn 的闭包。任
何我们希望储存到 Cacher 实例的 calculation 字段的闭包必须有一个 u32 参数(由 Fn 之后的括号的内
容指定)并必须返回一个 u32(由 −> 之后的内容)。
注意:函数也都实现了这三个 Fn trait。如果不需要捕获环境中的值,则可以使用实现了 Fn trait 的
函数而不是闭包。
字段 value 是 Option 类型的。在执行闭包之前,value 将是 None。如果使用 Cacher 的代码请
求闭包的结果,这时会执行闭包并将结果储存在 value 字段的 Some 成员中。接着如果代码再次请求闭
包的结果,这时不再执行闭包,而是会返回存放在 Some 成员中的结果。
刚才讨论的有关 value 字段逻辑定义于

struct Cacher

where

T: Fn(u32) -> u32,

{

calculation: T,

value: Option,

}

impl Cacher
where
T: Fn(u32) -> u32,
{
fn new(calculation: T) -> Cacher {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
v
}
}
}
}

fn main() {}

Cacher 结构体的字段是私有的,因为我们希望 Cacher 管理这些值而不是任由调用代码潜在的直接改变
他们。
Cacher::new 函数获取一个泛型参数 T,它定义于 impl 块上下文中并与 Cacher 结构体有着相同的 trait
bound。Cacher::new 返回一个在 calculation 字段中存放了指定闭包和在 value 字段中存放了 None
值的 Cacher 实例,因为我们还未执行闭包。
当调用代码需要闭包的执行结果时,不同于直接调用闭包,它会调用 value 方法。这个方法会检查
self .value 是否已经有了一个 Some 的结果值;如果有,它返回 Some 中的值并不会再次执行闭包。
如果 self .value 是 None,则会调用 self .calculation 中储存的闭包,将结果保存到 self .value 以便将
来使用,并同时返回结果值。
generate_workout 函数中利用 Cacher 结构体:

use std::thread;

use std::time::Duration;

struct Cacher

where

T: Fn(u32) -> u32,

{

calculation: T,

value: Option,

}

impl Cacher

where

T: Fn(u32) -> u32,

{

fn new(calculation: T) -> Cacher {

Cacher {

calculation,

value: None,

}

}

fn value(&mut self, arg: u32) -> u32 {

match self.value {

Some(v) => v,

None => {

let v = (self.calculation)(arg);

self.value = Some(v);

v

}

}

}

}

fn generate_workout(intensity: u32, random_number: u32) {
let mut expensive_result = Cacher::new(|num| {
println!(“calculating slowly…”);
thread::sleep(Duration::from_secs(2));
num
});
if intensity < 25 {
println!(“Today, do {} pushups!”, expensive_result.value(intensity));
println!(“Next, do {} situps!”, expensive_result.value(intensity));
} else {
if random_number == 3 {
println!(“Take a break today! Remember to stay hydrated!”);
} else {
println!(
“Today, run for {} minutes!”,
expensive_result.value(intensity)
);
}
}
}

fn main() {

let simulated_user_specified_value = 10;

let simulated_random_number = 7;

generate_workout(simulated_user_specified_value, simulated_random_number);

}

在 generate_workout 函数中利用 Cacher 结构体来抽象出缓存逻辑
不同于直接将闭包保存进一个变量,我们保存一个新的 Cacher 实例来存放闭包。接着,在每一个需要
结果的地方,调用 Cacher 实例的 value 方法。可以调用 value 方法任意多次,或者一次也不调用,而
慢计算最多只会运行一次。
main 函数来运行这段程序,并改变 simulated_user_specified_value 和
simulated_random_number 变量中的值来验证在所有情况下在多个 if 和 else 块中,闭包打印的
calculating slowly … 只会在需要时出现并只会出现一次。Cacher 负责确保不会调用超过所需的慢计算
所需的逻辑,这样 generate_workout 就可以专注业务逻辑了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值