Post

Rust 基本功 -- map 函数

Rust 基本功 -- map 函数

map 函数设计

rust 的 map() 函数设计原则有两种类别:

  • eager(execute immediately): 调用 map 时立刻执行转换逻辑
  • lazy(wait for use): 调用 map 时会返回一个包装类型, 不会立刻对内部的数据进行转换, 外部执行收集时才会转换.
Type Nature Behavior
Option<T> Eager Transforms Some, ignores None.
Result<T, E> Eager Transforms Ok, ignores Err.
Iterator Lazy Returns a Map struct; code runs only during iteration.
Ref / RefMut Eager Transforms a reference to a field/sub-part.
ControlFlow Eager Transforms the Continue variant.

一、Option::map (eager 类型)

Option::map 是 Rust 中处理 Option 类型最常用的方法之一,它允许我们在 Some 值上应用一个函数,同时保持 None 的情况不变。

1
2
3
4
5
6
7
8
9
10
11
impl<T> Option<T> {
    pub fn map<U, F>(self, f: F) -> Option<U>
    where
        F: FnOnce(T) -> U,
    {
        match self {
            Some(x) => Some(f(x)),
            None => None,
        }
    }
}

关键点:

  • map 接收 self(按值),这意味着它会消费原始的 Option<T>
  • 闭包 f 接收 T 类型的所有权,使用 FnOnce 是因为值可能被移动
  • 返回 Option<U>,其中 U 是闭包返回的类型
  • 立即执行f(x)map 调用时立即执行,结果被包装在 Some

示例:

1
2
3
4
5
6
let opt = Some(5);
let result = opt.map(|x| {
    println!("Processing {}", x);  // 立即打印
    x * 2
});
// 此时 result 已经是 Some(10),转换已经完成

二、Iterator::map (Lazy 类型)

Iterator::map 的设计与 Option::map 完全不同,它采用惰性求值(Lazy Evaluation)策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
trait Iterator {
    type Item;
    
    fn map<B, F>(self, f: F) -> Map<Self, F>
    where
        Self: Sized,
        F: FnMut(Self::Item) -> B,
    {
        Map::new(self, f)
    }
    
    fn next(&mut self) -> Option<Self::Item>;
}

关键点:

  • map 返回一个新的迭代器适配器 Map<Self, F>不立即执行转换
  • 闭包 f 使用 FnMut,因为可能在多次迭代中被调用
  • 实际的转换逻辑在调用 next() 时才会执行

2.1 Map 适配器的内部实现

上面我们看到 map 函数调用之后返回了一个 Map<Self, F> 对象, 下面看看这个 Map 的内部逻辑是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[stable(feature = "rust1", since = "1.0.0")]
#[derive(Clone)]
pub struct Map<I, F> {
    // Used for `SplitWhitespace` and `SplitAsciiWhitespace` `as_str` methods
    pub(crate) iter: I,
    f: F,
}

impl<I, F> Map<I, F> {
    pub(in crate::iter) fn new(iter: I, f: F) -> Map<I, F> {
        Map { iter, f }
    }
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<B, I: Iterator, F> Iterator for Map<I, F>
where
    F: FnMut(I::Item) -> B,
{
    type Item = B;

    #[inline]
    fn next(&mut self) -> Option<B> {
        // 关键:只有在调用 next() 时才执行闭包
        self.iter.next().map(&mut self.f)
    }

    // ...
}

有趣的设计细节:

  • #[must_use] 属性:提醒开发者迭代器是惰性的,必须被消费才会执行
  • Map 结构体只是包装了原始迭代器和闭包,不存储任何转换结果
  • 每次 next() 调用时,才从底层迭代器获取一个元素并应用闭包

2.2 惰性求值的实际表现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let vec = vec![1, 2, 3, 4, 5];

// 创建迭代器链,但此时没有任何计算发生
let mapped = vec.iter()
    .map(|x| {
        println!("Processing {}", x);  // 这行代码还没有执行!
        x * 2
    })
    .map(|x| {
        println!("Doubling {}", x);    // 这行代码也没有执行!
        x * 2
    });

// 即使创建了多个 map,仍然没有任何输出
println!("Iterator created, but no processing yet");

// 只有在消费迭代器时,闭包才会执行
for value in mapped {
    println!("Got: {}", value);
}
// 输出:
// Processing 1
// Doubling 2
// Got: 4
// Processing 2
// Doubling 4
// Got: 8
// ...
This post is licensed under CC BY 4.0 by the author.