您现在的位置是:首页 >技术交流 >研读Rust圣经解析——Rust learn-11(测试,迭代器,闭包)网站首页技术交流
研读Rust圣经解析——Rust learn-11(测试,迭代器,闭包)
研读Rust圣经解析——Rust learn-11(测试,迭代器,闭包)
测试
测试我不打算很详细的写,大家知道如何使用其实就差不多了
编写测试模块
一般来说我们在lib中编写测试
cargo new test_01 --lib
这样我们构建了一个test的lib
在这个工程里面你看到应该是有个lib.rs没有main.rs的
声明test模块
这里并不是声明一个mod,而是一个测试区域,在区域中可以写很多的测试方法
我们通过#[cfg(test)]
宏来进行标注
#[cfg(test)]
mod tests {
}
编写测试方法
测试方法和普通方法没什么区别,主要在于标注#[test]
#[test]
fn test01() {
assert_eq!(6, 4);
}
执行测试
我们使用cargo test
就可以启动进行测试了,测试会将所有标注#[test]
的方法都测试一遍
测试结果检查
我们通常使用assert检查测试结果
- assert:断言结果为true,否则panic
- assert_eq:断言两边相等,否则panic
- assert_ne:断言两边不等,否则panic
闭包
一个可以储存在变量里的类似函数的结构
Rust 的 闭包(closures)是可以保存在一个变量中或作为参数传递给其他函数的匿名函数。可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算。不同于函数,闭包允许捕获被定义时所在作用域中的值。我们将展示闭包的这些功能如何复用代码和自定义行为。
定义一个闭包
如下,我们使用||{}
定义了一个闭包,很像是函数的写法:
fn main() {
let a = || {
10
};
println!("{}",a());
}
这个闭包会返回10,但是实际a的类型是fn:fn->i32
,对于但语句来说将{}
省略也是OK的
完整写法
|参数|->返回值{
//...
}
闭包可以捕获环境
如下的闭包中使用到了b变量,但是b没有传入闭包,这就和函数有了差别,使得我们在做一些简单的,不用复用的操作的时候可以直接使用闭包而不是定义一个函数
fn main() {
let b = 16;
let a = |x:i32| {
b * x
};
println!("{}",a(10));
}
闭包类比函数
这里给出圣经中的例子:
fn add_one_v1 (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;
闭包类型推断
我们看这个程序,看上去没任何问题,但是实际上会报错,原因就是闭包类型推断,前面我们知道闭包会自动推断类型,所以当第一次执行的时候,闭包认为返回值是String,第二次再次调用相同的闭包导致了返回值类型不一致,所以报错:
fn main() {
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5);
}
闭包获取所有权
闭包体不严格需要所有权,如果希望强制闭包获取它用到的环境中值的所有权,可以在参数列表前使用 move 关键字
在将闭包传递到一个新的线程时这个技巧很有用,它可以移动数据所有权给新线程
move ||->返回值类型{
}
将被捕获的值移出闭包和 Fn trait
闭包捕获和处理环境中的值的方式影响闭包实现的 trait。Trait 是函数和结构体指定它们能用的闭包的类型的方式。取决于闭包体如何处理值,闭包自动、渐进地实现一个、两个或三个 Fn trait。
FnOnce
适用于能被调用一次的闭包,所有闭包都至少实现了这个 trait,因为所有闭包都能被调用。一个会将捕获的值移出闭包体的闭包只实现 FnOnce trait,这是因为它只能被调用一次。FnMut
适用于不会将捕获的值移出闭包体的闭包,但它可能会修改被捕获的值。这类闭包可以被调用多次。Fn
适用于既不将被捕获的值移出闭包体也不修改被捕获的值的闭包,当然也包括不从环境中捕获值的闭包。这类闭包可以被调用多次而不改变它们的环境,这在会多次并发调用闭包的场景中十分重要。
迭代器
迭代器模式允许你对一个序列的项进行某些处理。迭代器(iterator)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
迭代器是惰性的:调用方法使用迭代器前无效
创建一个迭代器
调用复合类型的iter方法即可创建,但此时无效
fn main() {
let arr = vec![1,2,3];
let item_iter = arr.iter();
}
应用迭代器遍历
以下是使用for对迭代器进行遍历
fn main() {
let mut arr = Vec::new();
arr.push(1);
arr.push(2);
let item_iter = arr.iter();
for item in item_iter {
println!("{}",item)
}
}
使用foreach配合闭包遍历
fn main() {
let mut arr = Vec::new();
arr.push(1);
arr.push(2);
let item_iter = arr.iter();
item_iter.for_each(|x|println!("{}",x))
}
通过模拟hasNext进行遍历
fn main() {
let mut arr = Vec::new();
arr.push(1);
arr.push(2);
let mut item_iter = arr.iter();
let mut i = item_iter.len();
while i != 0 {
println!("{}", item_iter.next().unwrap());
i = i - 1;
}
}
next方法
迭代器的next方法返回的是一个Option<T>
这样一个对象
sum方法
这个方法一样会消费迭代器,因为获取迭代器的所有权并反复调用 next 来遍历迭代器
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
迭代器适配器
Iterator trait 中定义了另一类方法,被称为 迭代器适配器(iterator adaptors),他们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。
fn main() {
let mut arr = Vec::new();
arr.push(1);
arr.push(2);
let new_arr:Vec<_> = arr.iter().map(|x| x + 4).collect();
println!("{:?}", new_arr);
}
这里我们使用map对迭代器中的每个元素进行操作,最后使用collect收集为新的集合类型
迭代器的性能
实际上迭代器更快,因为迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的 零成本抽象(zero-cost abstractions)之一,它意味着抽象并不会引入运行时开销
详细大家可以看这里:
https://kaisery.github.io/trpl-zh-cn/ch13-04-performance.html