• 函数式编程:Rust中的闭包与迭代器


    闭包 Closure

    闭包是一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值,例如:

    fn main() {
       let x = 1;
       let sum = |y| x + y;
    
        assert_eq!(3, sum(2));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上面的代码展示了非常简单的闭包 sum,它拥有一个入参 y,同时捕获了作用域中的 x 的值,因此调用 sum(2) 意味着将 2(参数 y)跟 1(x)进行相加,最终返回它们的和:3

    可以看到 sum 非常符合闭包的定义:可以赋值给变量,允许捕获调用者作用域中的值。

    Rust 闭包在形式上借鉴了 SmalltalkRuby 语言,与函数最大的不同就是它的参数是通过 |parm1| 的形式进行声明,如果是多个参数就 |param1, param2,...|, 下面给出闭包的形式定义:

    |param1, param2,...| {
        语句1;
        语句2;
        返回表达式
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果只有一个返回表达式的话,定义可以简化为:

    |param1| 返回表达式
    
    • 1

    下面展示了同一个功能的函数和闭包实现形式:

    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  ;
    
    • 1
    • 2
    • 3
    • 4

    注意:闭包中最后一行表达式返回的值,就是闭包执行后的返回值

    迭代器 Iterator

    迭代器允许我们迭代一个连续的集合,例如数组、动态数组 VecHashMap 等,在此过程中,只需关心集合中的元素如何处理,而无需关心如何开始、如何结束、按照什么样的索引去访问等问题。

    IntoIterator 特征拥有一个 into_iter 方法,因此我们还可以显式的把数组转换成迭代器:

    let arr = [1, 2, 3];
    for v in arr.into_iter() {
        println!("{}", v);
    }
    
    • 1
    • 2
    • 3
    • 4

    迭代器之所以成为迭代器,就是因为实现了 Iterator 特征,要实现该特征,最主要的就是实现其中的 next 方法,该方法控制如何从集合中取值,最终返回值的类型是关联类型 Item

    例子:模拟实现 for 循环

    因为 for 循环是迭代器的语法糖,因此我们完全可以通过迭代器来模拟实现它:

    let values = vec![1, 2, 3];
    
    {
        let result = match IntoIterator::into_iter(values) {
            mut iter => loop {
                match iter.next() {
                    Some(x) => { println!("{}", x); },
                    None => break,
                }
            },
        };
        result
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    IntoIterator::into_iter 是使用完全限定的方式去调用 into_iter 方法,这种调用方式跟 values.into_iter() 是等价的。

    同时我们使用了 loop 循环配合 next 方法来遍历迭代器中的元素,当迭代器返回 None 时,跳出循环。

    可以使用 into_iter 的方式将数组转化为迭代器,除此之外,还有 iteriter_mut,它们的区别如下:

    • into_iter 会夺走所有权
    • iter 是借用
    • iter_mut 是可变借用
    Iterator 和 IntoIterator 的区别

    这两个其实还蛮容易搞混的,但我们只需要记住,Iterator 就是迭代器特征,只有实现了它才能称为迭代器,才能调用 next

    IntoIterator 强调的是某一个类型如果实现了该特征,它可以通过 into_iteriter 等方法变成一个迭代器。

    使用 collect 收集成 HashMap 集合:

    use std::collections::HashMap;
    fn main() {
        let names = ["sunface", "sunfei"];
        let ages = [18, 18];
        let folks: HashMap<_, _> = names.into_iter().zip(ages.into_iter()).collect();
    
        println!("{:?}",folks);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    zip 是一个迭代器适配器,它的作用就是将两个迭代器的内容压缩到一起,形成 Iterator 这样的新的迭代器,在此处就是形如 [(name1, age1), (name2, age2)] 的迭代器。

    然后再通过 collect 将新迭代器中(K, V) 形式的值收集成 HashMap,同样的,这里必须显式声明类型,然后 HashMap 内部的 KV 类型可以交给编译器去推导,最终编译器会推导出 HashMap<&str, i32>,完全正确!

    闭包作为适配器参数

    之前的 map 方法中,我们使用闭包来作为迭代器适配器的参数,它最大的好处不仅在于可以就地实现迭代器中元素的处理,还在于可以捕获环境值:

    struct Shoe {
        size: u32,
        style: String,
    }
    
    fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
        shoes.into_iter().filter(|s| s.size == shoe_size).collect()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    filter 是迭代器适配器,用于对迭代器中的每个值进行过滤。 它使用闭包作为参数,该闭包的参数 s 是来自迭代器中的值,然后使用 s 跟外部环境中的 shoe_size 进行比较,若相等,则在迭代器中保留 s 值,若不相等,则从迭代器中剔除 s 值,最终通过 collect 收集为 Vec 类型。

    实现 Iterator 特征

    之前的内容我们一直基于数组来创建迭代器,实际上,不仅仅是数组,基于其它集合类型一样可以创建迭代器,例如 HashMap。 你也可以创建自己的迭代器 —— 只要为自定义类型实现 Iterator 特征即可。

    首先,创建一个计数器:

    struct Counter {
        count: u32,
    }
    
    impl Counter {
        fn new() -> Counter {
            Counter { count: 0 }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们为计数器 Counter 实现了一个关联函数 new,用于创建新的计数器实例。下面我们继续为计数器实现 Iterator 特征:

    impl Iterator for Counter {
        type Item = u32;
    
        fn next(&mut self) -> Option<Self::Item> {
            if self.count < 5 {
                self.count += 1;
                Some(self.count)
            } else {
                None
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    首先,将该特征的关联类型设置为 u32,由于我们的计数器保存的 count 字段就是 u32 类型, 因此在 next 方法中,最后返回的是实际上是 Option 类型。

    每次调用 next 方法,都会让计数器的值加一,然后返回最新的计数值,一旦计数大于 5,就返回 None

    最后,使用我们新建的 Counter 进行迭代:

     let mut counter = Counter::new();
    
    assert_eq!(counter.next(), Some(1));
    assert_eq!(counter.next(), Some(2));
    assert_eq!(counter.next(), Some(3));
    assert_eq!(counter.next(), Some(4));
    assert_eq!(counter.next(), Some(5));
    assert_eq!(counter.next(), None);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    实现 Iterator 特征的其它方法

    可以看出,实现自己的迭代器非常简单,但是 Iterator 特征中,不仅仅是只有 next 一个方法,那为什么我们只需要实现它呢?因为其它方法都具有默认实现,所以无需像 next 这样手动去实现,而且这些默认实现的方法其实都是基于 next 方法实现的。

    下面的代码演示了部分方法的使用:

    let sum: u32 = Counter::new()
        .zip(Counter::new().skip(1))
        .map(|(a, b)| a * b)
        .filter(|x| x % 3 == 0)
        .sum();
    assert_eq!(18, sum);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    其中 zipmapfilter 是迭代器适配器:

    • zip 把两个迭代器合并成一个迭代器,新迭代器中,每个元素都是一个元组,由之前两个迭代器的元素组成。例如将形如 [1, 2, 3, 4, 5][2, 3, 4, 5] 的迭代器合并后,新的迭代器形如 [(1, 2),(2, 3),(3, 4),(4, 5)]
    • map 是将迭代器中的值经过映射后,转换成新的值[2, 6, 12, 20]
    • filter 对迭代器中的元素进行过滤,若闭包返回 true 则保留元素[6, 12],反之剔除

    sum 是消费者适配器,对迭代器中的所有元素求和,最终返回一个 u3218

  • 相关阅读:
    Nuxt.js的详细使用
    【C++】类的封装 ④ ( 访问控制权限 | struct 和 class 关键字定义类的区别 | 类的默认访问权限 | 类的默认继承方式 )
    累加和为 K 的最长子数组问题
    一文学会Scala【Scala一站式学习笔记】
    UG NX二次开发(C#)-外部模式-导出dwg格式的文件
    信号完整性(SI)电源完整性(PI)学习笔记(三十一)电源分配网路(三)
    webgoat-Request Forgeries 请求伪造
    【CSS动效实战(纯CSS与JS动效)】02 flex 布局实战(仿 JD 及 gitCode 布局)及 media 自适应初探 下
    Qt Design Studio 4.5现已发布
    插件化编程之WebAPI统一返回模型
  • 原文地址:https://blog.csdn.net/qq_51601649/article/details/133966993