Rust: .foldと.reduceの違いと注意点

ハイサイ！オースティンやいびーん

# 概要

RustのVector（配列）を別の型に畳む時に使う`std::iter::Iterator.fold`と`std::iter::Iterator.reduce`の使い方と注意点を紹介します。

## なぜfoldとreduceを使うのか

foldとreduceは配列に入っている要素を一つの値に変換する時にとても強力なツールになります。

パフォーマンスの判断で場合によっては相応しくないのですが、困ったら基本的にこの二つのメソッドを使えば配列の操作ならなんでも可能です。

# 問題紹介

本記事の概要を説明するために、まず設問します。

Vectorに入った人の名前の文字列があります。様々な国々の名前も出てきます。[Sri Lankaの方のように](https://ameblo.jp/ekuchikiri/entry-12605097949.html)名前がとても長いこともあります。

そのVectorを英語の敬称付きの単一の文字列Stringに変換する関数を書きたいのです。

性別も引数として受けて敬称を変えます。

```rust
enum Sex {
    Male,
    Female,
}

fn get_name_with_title(name_parts: &Vec<String>, sex: &Sex) -> String {
  // implementation
}
```

# foldを使った場合

`fold`を使った場合、まず、初期値が必要です。まず、性別の敬称を作るロジックを追加しましょう。

```rust
fn get_title_by_sex(sex: &Sex) -> &'static str {
    match sex {
        Sex::Male => "Mr.",
        Sex::Female => "Ms.",
    }
}
```

> 戻り値の型を`&'static str`にすることによってこれらの敬称の`str`がランタイムのコードにコンパイルされるらしいのです！

この関数を使って初期値を生成しましょう。

```rust
fn get_name_with_title(name_parts: &Vec<String>, sex: Sex) -> String {
    let title: String = get_title_by_sex(sex).to_string();
    //
}
```

この初期値を使って`fold`をしたいのですが、まずVectorのIteratorを取得しないといけないのです。以下のように`name_parts`には`iter`というメソッドがあります：

```rust
name_parts.iter()
```

このメソッドが`Iterator`というものを返してくれて、それのメソッドに`fold`が入っています。

`fold`を使ってみましょう。

```rust
    name_parts
        .iter()
        .fold(title, |accumulator, part| accumulator + " " + part)
```

`fold`は一番目の引数に初期値をもらいます。それがコールバックの最初の`accumulator`の値になります。コールバックでは`accumulator`に文字列を足していきます。

初期値を渡しているので、**例えVectorの配列が空でも`String`を渡してくれます** ので時に注意することはないです。

全体で見るとこのように完成します。

```rust
fn get_name_with_title(name_parts: &Vec<String>, sex: &Sex) -> String {
    let title = get_title_by_sex(sex).to_string();

    name_parts
        .iter()
        .fold(title, |accumulator, part| accumulator + " " + part)
}
```

# .reduceで同じようなことをしようとしたら、

`.reduce`を使って同様な実装を試してみましょう。あらかじめ言っておきますが、今回の問題には`.reduce`があまり合いません。

`reduce`は`fold`と違って、引数に初期値を渡せません。その代わり、`reduce`はVectorの最初に値を`accumulator`にして処理をしてくれます。

前回と同様に単に`.fold`を変えてみましょう。

```rust

fn get_name_with_title_reduce(name_parts: &Vec<String>, sex: &Sex) -> String {
    let title = get_title_by_sex(sex).to_string();

    let full_name = name_parts
        .iter()
        .reduce(|accumulator, part| accumulator + " " + part);

    format!("{title} {full_name}")
}
```

このコードにはRustのコンパイラーを怒らせる箇所が二つありますが、読者はお分かりでしょうか。

### 初期値がないかもしれない問題

まず、`reduce`の結果が一つ大きな問題になっています。先ほど説明した通り、初期値はなく、配列の最初の値から始めます。では、**空の配列だったら、`reduce`の結果はどうなりますか？**

答えは、何も出てこない可能がある、つまりRustの`null`ならず`None`が帰ります：

```rust
let full_name: Option<String> = name_parts
        .iter()
        .reduce(|accumulator, part| accumulator + " " + part);
```
なので、`None`の可能性に対してなんらかの処理をしないといけません。

```rust
let full_name: Option<String> = name_parts
        .iter()
        .reduce(|accumulator, part| accumulator + " " + part);
let full_name = match full_name {
        Some(s) => s,
        None => String::new()
    };
```

もしくはもっと簡単に

```rust
let full_name = full_name.unwrap_or_default();
```

### 引数がReferenceという問題

まだ重大なエラーが潜んでいるのです。実は、`reduce`のコールバックに入ってくる`accumulator`と`part`は、`String`のreferenceでしかないのです。つまり、文字列を`accumulator`に足していけないのです。

なので、この問題を解消するにはいくつか手があります：

1. `.map`を使って、`&String`の要素を`String`に変換しておく
1. 「やっぱり`fold`があっていよね」と諦める

2が正しいのですが、それだと`reduce`を紹介できないので、この[とても役立つstackoverflowの知見](https://stackoverflow.com/questions/68854895/reduce-vs-fold-for-string-concatenation-in-rust)を拝借して以下のように解決します。

```rust
fn get_name_with_title_reduce(name_parts: &Vec<String>, sex: &Sex) -> String {
    let title = get_title_by_sex(sex).to_string();

    let full_name = name_parts
        .iter()
        .map(|part| part.to_string())
        .reduce(|accumulator, part| accumulator + " " + &part);
    let full_name = full_name.unwrap_or_default();

    format!("{title} {full_name}")
}
```

コード量も含めて、明らかに`fold`がこの問題にあっていますね:sweat_smile:。

しかし、上記のような問題で分かっていただいたように、**`reduce` には落とし穴がある**のです！

# 関数を使ってみる

両方とも実際に使ってみましょう。

```rust
fn main() {
    let name_vec = vec![
        "Austin".to_string(),
        "John".to_string(),
        "Mayer".to_string(),
    ];
    let s = Sex::Male;

    let name = get_name_with_title(&name_vec, &s);
    println!("Name: {}", name);

    let name = get_name_with_title_reduce(&name_vec, &s);
    println!("Name: {}", name);
}
```

STDOUT: 

```
Name: Mr. Austin John Mayer
Name: Mr. Austin John Mayer
```

どちらもうまくいきました！

# まとめ

`fold`と`reduce`の使い方を紹介してきましたが、いかがでしょうか？

解決しようとしていた問題に対して、本記事の命が危なくなった`reduce`の不適切さが浮き彫りになりましたが、ある意味それも参考になったらと思います。**適している問題とそうでない問題があります**。

プログラミングでは常に解決の道が百本あるのですが、そのもっとも近距離の道は一つしかありません。その道を見極められるかどうかは我々職人の技量にかかりますが、筆者はRustの勉強を通して自分の未熟さを改めて実感しました。

**Rustは難しい**。

しかし、メモリについて考えさせられるから、パフォーマンスについても真剣に考えるし、より良いプログラマーになる訓練になるかなと
願って
思っています！

筆者と同様にRustを勉強している方、まじゅん ちばらな！