匿名函数
创建
在前面的文档中有写过回调函数的逻辑,其实就是匿名函数。支持的写法有以下几种
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 borrowed
println!("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("
13123
123123
121
");
let nstr = str.lines()
.filter(|&x| x.contains("123"))
.collect::<Vec<&str>>()
.join("\n");
println!("{}", nstr);
}
性能比较
迭代器的性能相比于使用循环性能综合来看是会更好的,因为 Rust 的编译器会对迭代器进行一些优化,比如对已知大小的迭代器操作,Rust 会将循环展开并将数据注册到栈中来提升访问效率。
所以完全不需要担心使用迭代器这种高级封装会影响代码性能。