Rust 练习册 22:映射函数与泛型的威力

在 Rust 的学习旅程中,我们经常会遇到需要对集合进行转换的情况。比如,你有一个数字列表,想要计算每个数的平方;或者有一组字符串,想把它们都转成大写形式。这种操作在函数式编程中被称为 "map" 操作,它是一种非常基础且强大的模式。

今天我们就来深入探讨 Exercism 上的 "accumulate" 练习,看看如何用 Rust 实现一个灵活而强大的 map 函数。

问题描述

在开始之前,让我们先看一下问题的核心。我们有一个函数签名:

rust 复制代码
/// What should the type of _function be?
pub fn map(input: Vec<i32>, _function: ???) -> Vec<i32> {
    unimplemented!("Transform input vector {:?} using passed function", input);
}

我们的任务是完成这个函数,使其能够接收一个向量和一个函数,并返回一个新的向量,其中包含原向量中每个元素经过函数处理后的结果。

简单实现

最简单的实现方式是这样的:

rust 复制代码
pub fn map(input: Vec<i32>, function: fn(i32) -> i32) -> Vec<i32> {
    let mut result = Vec::new();
    for item in input {
        result.push(function(item));
    }
    result
}

这确实能工作,但存在一些限制。它只能接受特定类型的函数(fn(i32) -> i32),无法使用闭包捕获环境变量,也无法处理不同类型的输入和输出。

泛型实现

为了使我们的 map 函数更加强大和灵活,我们需要使用泛型。来看一个更完整的实现:

rust 复制代码
pub fn map<I, O, F>(input: Vec<I>, function: F) -> Vec<O>
where
    F: Fn(I) -> O,
{
    let mut result = Vec::new();
    for item in input {
        result.push(function(item));
    }
    result
}

在这个版本中:

  • I 表示输入向量中元素的类型
  • O 表示输出向量中元素的类型
  • F 是转换函数的类型,它实现了 Fn(I) -> O trait

这样,我们的函数就能处理任意类型的输入和输出了!

使用迭代器优化

Rust 的迭代器系统已经提供了 map 方法,我们可以利用它让代码更加简洁:

rust 复制代码
pub fn map<I, O, F>(input: Vec<I>, function: F) -> Vec<O>
where
    F: Fn(I) -> O,
{
    input.into_iter().map(function).collect()
}

这段代码简洁明了,它将输入向量转换为迭代器,应用映射函数,然后收集结果为新的向量。

测试案例解析

通过查看测试案例,我们可以看到这个函数的强大之处:

rust 复制代码
fn square(x: i32) -> i32 {
    x * x
}

#[test]
fn func_single() {
    let input = vec![2];
    let expected = vec![4];
    assert_eq!(map(input, square), expected);
}

这里展示了如何传递一个普通函数作为参数。

rust 复制代码
#[test]
fn closure() {
    let input = vec![2, 3, 4, 5];
    let expected = vec![4, 9, 16, 25];
    assert_eq!(map(input, |x| x * x), expected);
}

这个例子演示了如何使用闭包作为参数。

rust 复制代码
#[test]
fn change_in_type() {
    let input: Vec<&str> = vec!["1", "2", "3"];
    let expected: Vec<String> = vec!["1".into(), "2".into(), "3".into()];
    assert_eq!(map(input, |s| s.to_string()), expected);
}

特别值得注意的是,我们的函数甚至可以在不同类型之间转换,比如从 &strString

更进一步:支持闭包状态

有些测试还检查了闭包是否能维护状态:

rust 复制代码
#[test]
fn mutating_closure() {
    let mut counter = 0;
    let input = vec![-2, 3, 4, -5];
    let expected = vec![2, 3, 4, 5];
    let result = map(input, |x: i64| {
        counter += 1;
        x.abs()
    });
    assert_eq!(result, expected);
    assert_eq!(counter, 4);
}

为了支持这种有状态的闭包,我们必须使用 Fn trait 而不是普通的函数指针。

结论

通过这个练习,我们学到了几个重要的 Rust 概念:

  1. 泛型 - 如何编写适用于多种类型的代码
  2. trait bounds - 如何限制泛型类型必须实现的功能
  3. 函数作为参数 - 如何将函数或闭包传递给其他函数
  4. 闭包 - Rust 中匿名函数的概念及其强大功能

这些概念构成了 Rust 函数式编程的基础,也是标准库中 Iterator::map 方法的工作原理。掌握了这些知识,你就能够在自己的 Rust 项目中写出更加优雅和灵活的代码了。

相关推荐
云泽8082 小时前
C++ List 容器详解:迭代器失效、排序与高效操作
开发语言·c++·list
云帆小二2 小时前
从开发语言出发如何选择学习考试系统
开发语言·学习
光泽雨3 小时前
python学习基础
开发语言·数据库·python
q***49863 小时前
数据库操作与数据管理——Rust 与 SQLite 的集成
数据库·rust·sqlite
百***06013 小时前
python爬虫——爬取全年天气数据并做可视化分析
开发语言·爬虫·python
jghhh014 小时前
基于幅度的和差测角程序
开发语言·matlab
fruge4 小时前
自制浏览器插件:实现网页内容高亮、自动整理收藏夹功能
开发语言·前端·javascript
曹牧4 小时前
Java中处理URL转义并下载PDF文件
java·开发语言·pdf
未来之窗软件服务4 小时前
幽冥大陆(二十二)dark语言智慧农业电子秤读取——东方仙盟炼气期
开发语言·windows·golang·东方仙盟·东方仙盟sdk