您现在的位置是:首页 >技术交流 >研读Rust圣经解析——Rust learn-11(测试,迭代器,闭包)网站首页技术交流

研读Rust圣经解析——Rust learn-11(测试,迭代器,闭包)

简明编程 2023-06-02 12:00:03
简介研读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检查测试结果

  1. assert:断言结果为true,否则panic
  2. assert_eq:断言两边相等,否则panic
  3. 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。

  1. FnOnce 适用于能被调用一次的闭包,所有闭包都至少实现了这个 trait,因为所有闭包都能被调用。一个会将捕获的值移出闭包体的闭包只实现 FnOnce trait,这是因为它只能被调用一次。
  2. FnMut 适用于不会将捕获的值移出闭包体的闭包,但它可能会修改被捕获的值。这类闭包可以被调用多次。
  3. Fn 适用于既不将被捕获的值移出闭包体也不修改被捕获的值的闭包,当然也包括不从环境中捕获值的闭包。这类闭包可以被调用多次而不改变它们的环境,这在会多次并发调用闭包的场景中十分重要。

迭代器

迭代器模式允许你对一个序列的项进行某些处理。迭代器(iterator)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
迭代器是惰性的:调用方法使用迭代器前无效

创建一个迭代器

调用复合类型的iter方法即可创建,但此时无效

fn main() {
    let arr = vec![123];
    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

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。