rust 闭包(一)https://developer.aliyun.com/article/1391990
特征 Fn(u32) -> u32 从表面来看,就对闭包形式进行了显而易见的限制:该闭包拥有一个u32类型的参数,同时返回一个u32类型的值。
接着,为缓存实现方法:
impl<T> Cacher<T> where T: Fn(u32) -> u32, { fn new(query: T) -> Cacher<T> { Cacher { query, value: None, } } // 先查询缓存值 `self.value`,若不存在,则调用 `query` 加载 fn value(&mut self, arg: u32) -> u32 { match self.value { Some(v) => v, None => { let v = (self.query)(arg); self.value = Some(v); v } } } }
上面的缓存有一个很大的问题:只支持 u32 类型的值,若我们想要缓存 &str 类型,显然就行不通了,因此需要将 u32 替换成泛型 E,该练习就留给读者自己完成,具体代码可以参考这里
捕获作用域中的值
在之前代码中,我们一直在用闭包的匿名函数特性(赋值给变量),然而闭包还拥有一项函数所不具备的特性:捕获作用域中的值。
fn main() { let x = 4; let equal_to_x = |z| z == x; let y = 4; assert!(equal_to_x(y)); }
上面代码中,x 并不是闭包 equal_to_x 的参数,但是它依然可以去使用 x,因为 equal_to_x 在 x 的作用域范围内。
对于函数来说,就算你把函数定义在 main 函数体中,它也不能访问 x:
fn main() { let x = 4; fn equal_to_x(z: i32) -> bool { z == x } let y = 4; assert!(equal_to_x(y)); }
报错如下:
error[E0434]: can't capture dynamic environment in a fn item // 在函数中无法捕获动态的环境 --> src/main.rs:5:14 | 5 | z == x | ^ | = help: use the `|| { ... }` closure form instead // 使用
三种 Fn 特征
- FnOnce,该类型的闭包会拿走被捕获变量的所有权。Once 顾名思义,说明该闭包只能运行一次:
fn fn_once<F>(func: F) where F: FnOnce(usize) -> bool, { println!("{}", func(3)); println!("{}", func(4)); } fn main() { let x = vec![1, 2, 3]; fn_once(|z|{z == x.len()}) }
- FnMut,它以可变借用的方式捕获了环境中的值,因此可以修改该值:
fn main() { let mut s = String::new(); let update_string = |str| s.push_str(str); update_string("hello"); println!("{:?}",s); }
- Fn 特征,它以不可变借用的方式捕获环境中的值 让我们把上面的代码中 exec 的 F 泛型参数类型修改为 Fn(&'a str):
fn main() { let mut s = String::new(); let update_string = |str| s.push_str(str); exec(update_string); println!("{:?}",s); } fn exec<'a, F: Fn(&'a str)>(mut f: F) { f("hello") }
闭包作为函数返回值
但是如果要使用闭包作为函数返回值,该如何做?
先来看一段代码:
fn factory() -> Fn(i32) -> i32 { let num = 5; |x| x + num } let f = factory(); let answer = f(1); assert_eq!(6, answer);
上面这段代码看起来还是蛮正常的,用 Fn(i32) -> i32 特征来代表 |x| x + num,非常合理嘛,肯定可以编译通过, 可惜理想总是难以照进现实,编译器给我们报了一大堆错误,先挑几个重点来看看:
fn factory<T>() -> Fn(i32) -> i32 { | ^^^^^^^^^^^^^^ doesn't have a size known at compile-time // 该类型在编译器没有固定的大小
Rust 要求函数的参数和返回类型,必须有固定的内存大小,例如 i32 就是 4 个字节,引用类型是 8 个字节,总之,绝大部分类型都有固定的大小,但是不包括特征,因为特征类似接口,对于编译器来说,无法知道它后面藏的真实类型是什么,因为也无法得知具体的大小。
同样,我们也无法知道闭包的具体类型,该怎么办呢?再看看报错提示:
help: use `impl Fn(i32) -> i32` as the return type, as all return paths are of type `[closure@src/main.rs:11:5: 11:21]`, which implements `Fn(i32) -> i32` | 8 | fn factory<T>() -> impl Fn(i32) -> i32 {
嗯,编译器提示我们加一个 impl 关键字,哦,这样一说,读者可能就想起来了,impl Trait 可以用来返回一个实现了指定特征的类型,那么这里 impl Fn(i32) -> i32 的返回值形式,说明我们要返回一个闭包类型,它实现了 Fn(i32) -> i32 特征。
完美解决,但是,在特征那一章,我们提到过,impl Trait 的返回方式有一个非常大的局限,就是你只能返回同样的类型,例如:
fn factory(x:i32) -> impl Fn(i32) -> i32 { let num = 5; if x > 1{ move |x| x + num } else { move |x| x - num } }
运行后,编译器报错:
error[E0308]: `if` and `else` have incompatible types --> src/main.rs:15:9 | 12 | / if x > 1{ 13 | | move |x| x + num | | ---------------- expected because of this 14 | | } else { 15 | | move |x| x - num | | ^^^^^^^^^^^^^^^^ expected closure, found a different closure 16 | | } | |_____- `if` and `else` have incompatible types |
嗯,提示很清晰:if 和 else 分支中返回了不同的闭包类型,这就很奇怪了,明明这两个闭包长的一样的,好在细心的读者应该回想起来,本章节前面咱们有提到:就算签名一样的闭包,类型也是不同的,因此在这种情况下,就无法再使用 impl Trait 的方式去返回闭包。
只需要用 Box 的方式即可实现:
fn factory(x:i32) -> Box<dyn Fn(i32) -> i32> { let num = 5; if x > 1{ Box::new(move |x| x + num) } else { Box::new(move |x| x - num) } }
至此,闭包作为函数返回值就已完美解决,若以后你再遇到报错时,一定要仔细阅读编译器的提示,很多时候,转角都能遇到爱。