Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rust Functional Programming 学习笔记

来源:Rust Unofficial Patterns

原文版本:main@f279f35 (2026-03-29 查验)

部分:Part 4 - Functional Programming (函数式编程)


概述

函数式编程范式在 Rust 中的应用和实践。

共 3 个小节


目录


5.1 Programming paradigms

核心命令式描述“如何做“,声明式描述“做什么“。

命令式示例

#![allow(unused)]
fn main() {
let mut sum = 0;
for i in 1..11 {
    sum += i;
}
println!("{sum}");
}
  • 需要像编译器一样逐步跟踪状态变化
  • 这是大多数人学习编程的起点

声明式示例

#![allow(unused)]
fn main() {
println!("{}", (1..11).fold(0, |a, b| a + b));
}
  • fold 是函数组合(来自 Haskell 的约定)
  • 描述“对 1 到 10 进行累加“,而非具体步骤

关键对比

方面命令式声明式
描述how 如何做what 做什么
状态显式跟踪变化隐藏状态变化
思维步骤序列函数组合
Rust 中的体现for/while 循环iter().map().fold()

Rust 鼓励声明式:Iterator 链式调用


5.2 Generics as Type Classes

核心:Rust 的泛型设计更像函数式语言(如 Haskell),泛型参数实际上是类型类约束,不同的泛型参数会创建不同的类型

关键概念 - Monomorphization(单体化/单态化)

  • 编译时将泛型代码转换为多个具体类型的独立版本
  • Vec<i32>Vec<char>两个不同的类型
  • 编译器会为每个具体类型生成独立代码
  • 优点:运行时无开销;缺点:代码膨胀

三种语言泛型机制对比

特性JavaC++Rust
实现机制类型擦除模板单体化泛型单体化
运行时泛型信息❌ 无❌ 无❌ 无
代码生成1 份代码,运行时转换每类型 1 份每类型 1 份
二进制大小✅ 小❌ 大❌ 大
运行效率⚠️ 有转换开销✅ 无间接层✅ 无间接层
特化方法❌ 不支持✅ 模板特化✅ 多 impl 块
类型安全编译时 + 运行时编译时编译时

验证结论:

💭 笔记作者观点:

  • 书中表述有误:将 C++ 和 Java 混为一谈,两者机制完全不同
  • C++ 和 Rust 泛型本质相同:都是编译时单体化
  • Java 泛型 = 类型擦除:运行时不存在泛型信息,用 Object 替代
  • Rust 的优势impl 块语法比 C++ 模板特化更简洁,与 trait 系统深度整合

问题示例 - 运行时 vs 编译时决策

#![allow(unused)]
fn main() {
// ❌ 运行时决策:用枚举包装
enum AuthInfo {
    Nfs(crate::nfs::AuthInfo),
    Bootp(crate::bootp::AuthInfo),
}

struct FileDownloadRequest {
    file_name: PathBuf,
    authentication: AuthInfo,
    mount_point: Option<PathBuf>,  // 只有 NFS 需要
}

// 调用者必须处理 None
fn mount_point(&self) -> Option<&Path> {
    self.mount_point.as_ref()
}
}

✅ 泛型解决方案 - 编译时分离 API

#![allow(unused)]
fn main() {
// 1. 定义协议 trait
pub(crate) trait ProtoKind {
    type AuthInfo;
    fn auth_info(&self) -> Self::AuthInfo;
}

// 2. 具体协议类型
pub struct Nfs { /* ... */ }
impl ProtoKind for Nfs { /* ... */ }

pub struct Bootp;
impl ProtoKind for Bootp { /* ... */ }

// 3. 泛型请求类型
struct FileDownloadRequest<P: ProtoKind> {
    file_name: PathBuf,
    protocol: P,
}

// 4. 通用方法
impl<P: ProtoKind> FileDownloadRequest<P> {
    fn file_path(&self) -> &Path { &self.file_name }
    fn auth_info(&self) -> P::AuthInfo { self.protocol.auth_info() }
}

// 5. 特定协议方法
impl FileDownloadRequest<Nfs> {
    fn mount_point(&self) -> &Path { self.protocol.mount_point() }
}
}

使用效果

#![allow(unused)]
fn main() {
// ❌ 编译错误:Bootp 没有 mount_point() 方法
let request: FileDownloadRequest<Bootp> = ...;
request.mount_point();  // ❌ 编译错误!
}

优势

  • 编译时错误检测,而非运行时检查
  • 共享字段去重
  • impl 块按状态组织,更清晰

劣势

  • 增加二进制大小(单体化导致)

实际应用场景

场景例子
标准库Vec<u8> 可从 CString 转换
嵌入式embedded-hal 静态验证引脚配置
HTTPhyper 不同连接器有不同方法
Type State 模式对象基于内部状态获得/失去 API

替代方案

  • 需要“分割 API“ → 考虑 Builder Pattern
  • API 相同仅行为不同 → 考虑 Strategy Pattern

泛型 = 类型类约束,不同参数 = 不同类型;优势是编译时检查,代价是二进制大小增加


5.3 Functional Optics

核心:Optics(光学/透镜)是函数式语言的 API 设计模式,用于组合行为和属性。Rust 不直接支持 Optics,但这个概念有助于理解某些 API(如 Serde)。

三种 Optics

1. The Iso(同构)

最简单的值转换器,一对函数在两个类型间转换。

#![allow(unused)]
fn main() {
struct ConcordanceSerde {}
impl ConcordanceSerde {
    fn serialize(value: Concordance) -> String { /* A → B */ }
    fn deserialize(value: String) -> Concordance { /* B → A */ }
}
}

特点:两个固定类型之间的转换。

2. The Poly Iso(多态同构)

允许泛型类型,返回单一类型。

#![allow(unused)]
fn main() {
// 标准库中的例子
pub trait FromStr: Sized {
    type Err;
    fn from_str(s: &str) -> Result<Self, Self::Err>;
}

pub trait ToString {
    fn to_string(&self) -> String;
}
}

问题

  • to_string 不指明格式(JSON? XML?)
  • 每个类型都要手写实现,难以扩展

3. The Prism(棱镜)

“更高一层“的泛型,支持多种格式。

Serde[T, F]:
  serialize: T, F -> String
  deserialize: String, F -> Result[T, Error]
  • T = 数据类型(User, Product, Order…)
  • F = 格式类型(JSON, YAML, XML, CBOR…)

Serde 的三层架构

层级Trait职责谁实现
顶层Serialize/Deserialize用户数据类型用户(宏生成)
中层Visitor构造/析构逻辑宏生成
底层Serializer/Deserializer格式特定实现格式库
┌─────────────────────────────────────┐
│ 顶层:Deserialize                  │ ← User::deserialize()
│   #[derive(Deserialize)]           │   创建 Visitor 并传递
│   struct User { ... }              │
├─────────────────────────────────────┤
│ 中层:Visitor                      │ ← UserVisitor::visit_map()
│   visit_map, visit_str, visit_u64  │   被 Deserializer 驱动
├─────────────────────────────────────┤
│ 底层:Deserializer                 │ ← JsonDeserializer
│   解析 JSON 字节,调用 Visitor      │   解析格式,反向调用
└─────────────────────────────────────┘

数据访问层:两种形式

形式 1:基础类型直接传值

#![allow(unused)]
fn main() {
pub trait Visitor<'de> {
    // 基础类型:直接传递值
    fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>;
    fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>;
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>;
    fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>;
}
}

形式 2:复杂类型用访问 trait

#![allow(unused)]
fn main() {
pub trait Visitor<'de> {
    // 复杂类型:需要进一步访问,按需解析
    fn visit_map<V>(self, visitor: V) -> Result<Self::Value, V::Error>
    where V: MapAccess<'de>;

    fn visit_seq<V>(self, visitor: V) -> Result<Self::Value, V::Error>
    where V: SeqAccess<'de>;

    fn visit_enum<V>(self, visitor: V) -> Result<Self::Value, V::Error>
    where V: EnumAccess<'de>;
}
}

为什么有这种区分

  • 基础类型:值已解析完成,直接传递
  • 复杂类型:需要进一步访问,按需解析

MapAccess 的作用

为什么需要 MapAccess

问题没有 MapAccess有 MapAccess
耦合度Visitor 绑定具体 DeserializerVisitor 只依赖 trait
实现次数N 类型 × M 格式 = N×M 次N 类型 + M 格式 = N+M 次
解析方式一次性加载所有数据按需解析,流式支持

MapAccess trait 定义

#![allow(unused)]
fn main() {
pub trait MapAccess<'de> {
    type Error: Error;
    
    // 方法级泛型:每次调用可以是不同类型
    fn next_key<K>(&mut self) -> Result<Option<K>, Self::Error>
    where K: Deserialize<'de>;
    
    fn next_value<V>(&mut self) -> Result<V, Self::Error>
    where V: Deserialize<'de>;
}
}

类型约束关系

类型参数级别绑定到决定方
'detrait 级MapAccess<'de>Deserializer
type Error关联类型MapAccessDeserializer
V (visit_map)方法级visit_mapDeserializer
K (next_key)方法级next_keyVisitor
V (next_value)方法级next_valueVisitor

具体实现

格式库V 的具体类型
serde_jsonJsonMapAccess<'de>
serde_yamlYamlMapAccess<'de>
serde_cborCborMapAccess<'de>
serde_xmlXmlMapAccess<'de>

完整交互流程

#![allow(unused)]
fn main() {
// 用户代码
let user: User = serde_json::from_str(json)?;

// 内部调用序列:
// 1. from_str 创建 JsonDeserializer
// 2. User::deserialize(deserializer) 被调用
//    - 创建 UserVisitor
//    - deserializer.deserialize_map(UserVisitor)
// 3. Deserializer 解析 JSON,创建 JsonMapAccess
// 4. Deserializer 调用 visitor.visit_map(JsonMapAccess)
// 5. Visitor 调用 map.next_key::<String>() 和 map.next_value::<u32>()
// 6. Visitor 构造并返回 User
}

核心机制 - 控制反转

组件职责不关心
Deserialize知道如何构造自己数据格式
Visitor定义构造步骤数据格式
Deserializer解析格式,驱动 Visitor目标类型
MapAccess抽象 map 访问接口具体格式

优势:N 个数据类型 × M 个格式 = N + M 次实现(而非 N×M)

Serde 通过三层架构(Deserialize + Visitor + Deserializer)和数据访问层(MapAccess 等 trait)实现 Prism,让数据类型和格式解耦


本部分学习总结

核心收获

  1. 命令式 vs 声明式

    • 命令式:描述how如何做(for 循环 + 状态变化)
    • 声明式:描述what做什么(fold 函数组合)
    • Rust 鼓励声明式:Iterator 链式调用
  2. 泛型作为类型类约束

    • Java 泛型 = 类型擦除:运行时不存在泛型信息,用 Object 替代
    • C++ 和 Rust 泛型 = 单体化:编译时为每个类型生成独立代码
    • 书中表述有误:C++ 和 Java 机制完全不同
    • Rust 的 impl 块语法比 C++ 模板特化更简洁
    • 应用:Type State 模式,用泛型将运行时检查移到编译时
  3. Functional Optics

    • Iso:两固定类型转换
    • Poly Iso:泛型转换
    • Prism:支持多格式(Serde 的核心)
    • Serde 三层架构:Deserialize + Visitor + Deserializer
    • 数据访问层:
      • 基础类型:直接传值(visit_u64, visit_str
      • 复杂类型:访问 trait(MapAccess, SeqAccess, EnumAccess
    • MapAccess 解耦 Visitor 和 Deserializer,支持按需解析
    • 核心机制:Deserializer 反向调用 Visitor

实践指导

场景推荐做法
编程思维优先声明式(Iterator 链式调用)
复杂对象构建用泛型分离 API(Type State 模式)
序列化/反序列化使用 Serde,理解三层架构
API 设计用 trait 解耦,支持扩展