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 项目中写出更加优雅和灵活的代码了。

相关推荐
2501_921649492 小时前
企业定制金融数据 API:从架构设计到 Python 接入实战
大数据·开发语言·python·websocket·金融·量化
直奔標竿2 小时前
SpringAI + RAG + MCP + Agent 零基础全栈实战(完结篇)| 27课完整汇总,Java开发者AI转型必看
java·开发语言·人工智能·spring boot·后端·spring
reasonsummer2 小时前
【教学类-160-13】20260422 AI视频培训-练习013“豆包AI视频《师幼互动》+豆包图片风格:CG动画”
开发语言·python
机器觉醒时代2 小时前
英伟达GR00T N系列四代模型演进解析
人工智能·机器人·具身智能·vla模型
曹牧2 小时前
Java:处理 HTTP 请求的 Content-Type
java·开发语言
itzixiao3 小时前
L1-066 猫是液体(5分)[java][python]
java·开发语言·python·算法
爆打维c3 小时前
详解 ROS计算图资源的命名与解析
机器人
Lightning-py3 小时前
Python 配置日志(Logging)
开发语言·python
Alphapeople3 小时前
夸父机器人使用案例
机器人
隔窗听雨眠3 小时前
MySQL主从延迟根因诊断法
开发语言·php