Rust で C# 9.0 と同じ（ような）プログラムを書いてみよう

この記事は [Rust 3 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/rust3) 9日目の記事です。

## 動機
[C# 1.0 と C# 9.0 で同じプログラムを書いてみよう](https://qiita.com/okazuki/items/53592fdbe3eba208b6e0) という記事の C# 9.0 のコードを Rust に写経したらどうなるか？という試みです。

## 環境
stable-x86_64-unknown-linux-gnu
rustc 1.48.0 (7eac88abb 2020-11-16)

## [end と入力するまで文字列を受け取って最後に入力した文字列から表示する](https://qiita.com/okazuki/items/53592fdbe3eba208b6e0#c-90)

`Iterator`を用いることで下記のように、とてもエレガントで直感的な記述が可能となります。また、ある入力を除外したり変換したい場合に`filter`や`map`などの関数を使うことでスマートに書けますね。

```rust:main.rs
use std::io;

struct ReadLine;

impl Iterator for ReadLine {
    type Item = String;
    fn next(&mut self) -> Option<Self::Item> {
        let mut buf = String::new();
        io::stdin().read_line(&mut buf).ok()?;
        Some(buf.trim().into())
    }
}

fn main() {
    ReadLine
        .take_while(|s| s.ne("end"))
        // .filter(|s| s.ne("a"))
        // .map(|s| format!("{}!", s))
        .collect::<Vec<_>>()
        .iter()
        .rev()
        .for_each(|s| println!("{}", s));
}
```

## [特定のメッセージを受け取ったらイベントを発行するクラス](https://qiita.com/okazuki/items/53592fdbe3eba208b6e0#c-90-1)

C# 9.0 の`EventHandler`や`EventArgs`が何ものかよくわからなかったので、とりあえず構造体のメンバにクロージャを渡して、ある条件でそのクロージャを呼ぶという実装を考えてみました。Rust においてクロージャは`Fn`、`FnMut`、`FnOnce`トレイトによって実現されている訳ですが、それらのトレイトについては[こちらの記事](https://keens.github.io/blog/2016/10/10/rustnokuro_ja3tanewotsukutterikaisuru/)や、[こちらの記事](https://qiita.com/shortheron/items/c1735dc4c7c78b0b55e9)を読むことで理解が深まると思います。

また、条件分岐には`matches!`マクロを使用していますが、それについては [matches! マクロの定義](https://doc.rust-lang.org/stable/src/core/macros/mod.rs.html#244-251) と [match 式のドキュメント](https://doc.rust-lang.org/reference/expressions/match-expr.html)が参考になります。

```rust:main.rs
struct MessageCollector<F: Fn(String)> {
    greet: Option<F>,
}

impl<F: Fn(String)> MessageCollector<F> {
    fn new() -> Self {
        Self { greet: None }
    }

    fn add_message(&self, message: &str) {
        if matches!(message, "おはようございます" | "こんにちは" | "こんばんは")
        {
            if let Some(greet) = self.greet.as_ref() {
                greet(message.into());
            }
        }
    }
}

fn main() {
    let greet = |message| {
        println!("{} って言ったね！", message);
    };
    let mut message_collector = MessageCollector::new();
    message_collector.greet = Some(greet);
    message_collector.add_message("Hello");
    message_collector.add_message("world");
    message_collector.add_message("こんにちは");
}
```

## [非同期でインターネットからテキストのダウンロード](https://qiita.com/okazuki/items/53592fdbe3eba208b6e0#c-90-2)

HTTP リクエストの定番は[`reqwest`](https://crates.io/crates/reqwest)クレートですね。2020 年 12 月現在`reqwest`のバージョンは 0.10.9 ですが、[`tokio`](https://crates.io/crates/tokio)バージョン 0.2.* にしか対応していないようです。（現在`tokio`の最新バージョンは 0.3.5）

```toml:Cargo.toml
[dependencies]
tokio = { version = "0.2", features = ["full"] }
reqwest = "0.10"
```

```rust:main.rs
use reqwest::Result;

#[tokio::main]
async fn main() -> Result<()> {
    let body = reqwest::get("https://example.com").await?.text().await?;
    println!("{}", body);
    Ok(())
}
```

## [テキストファイルに書かれた URL のリストからサイトのタイトルタグの行を取得](https://qiita.com/okazuki/items/53592fdbe3eba208b6e0#c-90-3)

非同期イテレータは Rust では`Stream`というそうです。C# 9.0 の実装とはちょっと違いますが、非同期イテレーションの例ということで…。

### [async-std](https://crates.io/crates/async-std) 版

非同期ランタイムに`async-std`を使用した場合の例。[`parallel-stream`](https://crates.io/crates/parallel-stream)という便利クレートのおかげで非同期イテレーションが簡単に書けます。

```toml:Cargo.toml
[dependencies]
anyhow = "1.0"
async-std = { version = "1.7", features = ["attributes"] }
parallel-stream = "2.1"
surf = "2.1"
```

```rust:main.rs
use anyhow::Result;
use async_std::fs;
use parallel_stream::prelude::*;
use surf;

#[async_std::main]
async fn main() -> Result<()> {
    let url_list = fs::read_to_string("urllist.txt")
        .await?
        .lines()
        .map(String::from)
        .collect::<Vec<_>>();

    url_list
        .into_par_stream()
        .map(|url| async move {
            let body = surf::get(&url).await.ok()?.body_string().await.ok()?;
            for line in body.lines() {
                if line.contains("<title>") {
                    return Some((url, line.to_string()));
                }
            }
            None
        })
        .for_each(|result| async {
            if let Some((url, title)) = result {
                println!("{}", url);
                println!("{}", title);
            }
        })    
        .await;
    
    Ok(())
}
```

### [tokio](https://crates.io/crates/tokio) 版

非同期ランタイムに`tokio`を使用した場合の例。非同期イテレーションには[`futures`](https://crates.io/crates/futures)クレートの`futures::stream`を使います。

```toml:Cargo.toml
[dependencies]
anyhow = "1.0"
tokio = { version = "0.2", features = ["full"] }
reqwest = "0.10"
futures = "0.3"
```

```rust:main.rs
use anyhow::Result;
use futures::{stream, StreamExt};
use tokio::fs;

#[tokio::main]
async fn main() -> Result<()> {
    let url_list = fs::read_to_string("urllist.txt")
        .await?
        .lines()
        .map(String::from)
        .collect::<Vec<_>>();

    stream::iter(url_list)
        .map(|url| async move {
            let body = reqwest::get(&url).await.ok()?.text().await.ok()?;
            for line in body.lines() {
                if line.contains("<title>") {
                    return Some((url, line.to_string()));
                }
            }
            None
        })
        .for_each_concurrent(3, |result| async {
            if let Some((url, title)) = result.await {
                println!("{}", url);
                println!("{}", title);
            }
        })
        .await;

    Ok(())
}
```


## [入力 2 つを整数かどうか判定して、さらに偶数かどうかも判定してメッセージを出しわける](https://qiita.com/okazuki/items/53592fdbe3eba208b6e0#c-90-4)

これは前述の`match`式の例ですね。つよい。

```rust:main.rs
use anyhow::Result;
use std::io;

fn main() -> Result<()> {
    let x1 = read_line()?.parse::<i32>().ok();
    let x2 = read_line()?.parse::<i32>().ok();

    let message = match (x1, x2) {
        (Some(v1), Some(v2)) if is_even(v1) && is_even(v2) => {
            format!("両方偶数！！入力した値は {} と {} ですね！", v1, v2)
        }
        (Some(v1), Some(v2)) => format!("入力した値は {} と {} ですね！", v1, v2),
        (Some(_), None) | (None, Some(_)) => "おしいね…".into(),
        (None, None) => "まだまだだね".into(),
    };

    println!("{}", message);
    Ok(())
}

fn is_even(value: i32) -> bool {
    value % 2 == 0
}

fn read_line() -> Result<String> {
    let mut buf = String::new();
    io::stdin().read_line(&mut buf)?;
    Ok(buf.trim().into())
}
```

## まとめ
C# 9.0 を写経してみたら Rust の便利な構文やクレートがあることに気付きました。