匿名函数
创建
在前面的文档中有写过回调函数的逻辑,其实就是匿名函数。支持的写法有以下几种
let add_one_v2 = |x: u32| -> u32 { x + 1 };let add_one_v3 = |x| { x + 1 };let add_one_v4 = |x| x + 1 ;
换成实际的例子就是
use std::error::Error;fn main() {let test = |x| x;println!("{}", test(1));let a = String::from("1");let test2 = |x: u32| -> Result<u32, Box<dyn Error>> {let s: u32 = a.parse()?;Ok(s + x)};println!("{}", test2(3).unwrap());}
类似于 JS 中的
const abc = () => { ... }的感觉。
闭包
在 Rust 中也存在闭包,概念跟 JS 中的类似,在匿名函数中可以感知当前 scope 的环境,并且拿到环境中的变量进行操作( 上面的例子里也有示范到 ),但是实际的表现会有不少差异。
fn main() {let a = 1;let my_closure = || {println!("output a here, {}", a)};my_closure();}
除了上面的简单例子,还有其他闭包的用法,比如在某个函数返回一个匿名函数,然后在返回的匿名函数中操作闭包中的变量,这种玩法在 JS 中经常实现,但是在 Rust 中需要借助 move 关键词,因为 Rust 会在 scope 结束的时候自动释放 scope 中的内存占用,所以需要通过 move 关键词,将闭包中的变量借用到匿名函数中,比如
fn main() {let b = get_closure();println!("{}", b(1));}fn get_closure() -> impl Fn(u32) -> u32 {let a = 6;move |x: u32| -> u32 {a + x}}
Move
move 的使用场景除了上面举的返回匿名函数的例子,还有一些其他场景和表现值得记录一下,比如下面的示例中,a2 在被借用的时候 ownership 转移到匿名函数中了,后面就无法再用 a2 了。
fn main() {let a2 = String::from("1");let test3 = move |x: u32| -> u32 {let r: u32 = a2.parse().unwrap();x + r};println!("{}", test3(3));// can not use a2 anymore}
如果是整型这种存储在 Stack 中的数据配合 move 又是另一种情况,因为整型在被借用的时候是用 Copy 的形式,所以纵使 test3 函数执行的时候,a2 被重新赋值,在 test3 中的 a2 仍然是 1 。
注意,这一点跟 JS 的逻辑是完全不同的。
fn main() {let mut a2 = 1;let test3 = move |x: u32| -> u32 {a2 + x};a2 = 3;println!("a2 = {}", a2);println!("a2 + x = {}", test3(1));}
如果不带 move 后面又有继续使用 test3 的话,a2 是不允许重新赋值的( 这个可以看 ownership 的文档,有说 Rust 会控制避免数据竞争的情况。 ),比如下面的例子就会抛异常。
fn main() {let mut a2 = 1;let test3 = |x: u32| -> u32 {a2 + x};a2 = 3; // throw error: cannot assign to `a2` because it is borrowedprintln!("a2 = {}", a2);println!("a2 + x = {}", test3(1));}
迭代器
用法
迭代器允许开发者有序的操作数据。
fn main() {let vec = vec![1, 2, 3];let iter = vec.iter();for x in iter {println!("{}", x);}}
或者通过 next 函数,返回的是 Option<T> ,比如下面的例子,前面三个返回是 Some<x> ,最后一个返回就是 None 了。执行 next 方法的时候会变更迭代器内部的状态,所以需要用 let mut。
fn main() {let vec = vec![1, 2, 3];let mut iter = vec.iter();println!("{:?}", iter.next());println!("{:?}", iter.next());println!("{:?}", iter.next());println!("{:?}", iter.next());}
用 for in 也可以改数据,通过 iter_mut 可以返回一个 mutable 的迭代器。
fn main() {let mut vec = vec![1, 2, 3];let iter = vec.iter_mut();for x in iter {*x += 1}}
还可以用 into_inter 将列表的 ownership 转移到迭代器里
fn main() {let vec = vec![1, 2, 3];let iter = vec.into_iter();println!("{:?}", vec); // 这里会报错,因为 vec 的 ownership 已经转移}
如果只需要读队列数据,可以用
iter(),如果需要操作队列数据,用iter_mut(),如果想转移队列的 ownership,则用into_iter()。
链式调用
迭代器最常用的还是链式调用。
fn main() {let vec2 = vec![1, 2, 3];let iter2 = vec2.into_iter();let r = iter2.zip(vec![1, 2, 3]).map(|(x, y)| x * y * 2).filter(|&x| {println!("filter here");x > 2}).collect::<Vec<u64>>();println!("{:?}", r);}
迭代器的链式调用中,比如 zip map 这些方法,都只是注册一个行为,需要最后有个消费行为才会真正执行,消费行为就是比如 collect ,sum ,或者 for_each 等。
其中 collect 会转换为容器类型返回,sum 是累加,for_each 是逐个操作,比如打印等。
就拿上面的例子,如果把最后的 collect 逻辑移除,那么 filter here 那段日志就不会打印了。
迭代器的链式调用也可以用于之前的字符匹配例子中
fn main() {let str = String::from("13123123123121");let nstr = str.lines().filter(|&x| x.contains("123")).collect::<Vec<&str>>().join("\n");println!("{}", nstr);}
性能比较
迭代器的性能相比于使用循环性能综合来看是会更好的,因为 Rust 的编译器会对迭代器进行一些优化,比如对已知大小的迭代器操作,Rust 会将循环展开并将数据注册到栈中来提升访问效率。
所以完全不需要担心使用迭代器这种高级封装会影响代码性能。
