TAKOYAKING’s blog 一覧

TAKOYAKING’s blog

たこ焼き系

Rust: enumのコンストラクターは関数のようにふるまえる

stringの限界 | Rust by Example
Rust Examplesより

map_errの引数は関数であるのにenumを直接ドーンと代入している箇所があって、どういうことか疑問に思ったので調べてみました。

現象

map_errの引数はFnOnce(E) -> Fであるのに関数の代わりにenumを渡してもきちんと処理されていた。

use std::num::ParseIntError;
use std::fmt;

type Result<T> = std::result::Result<T, DoubleError>;

enum DoubleError {
    // このエラーに関しては、エラーの詳細を説明するのに追加の情報は必要ない。
    EmptyVec,
    // パースに失敗した場合はParseIntErrorの挙動に追従する。追加の情報を持たせたい
    // 場合は、この型に、より多くのデータを追加しなくてはならない。
    Parse(ParseIntError),
}

fn double_first(vec: Vec<&str>) -> Result<i32> {
    vec.first()
       // エラーを新しい型のものに変換
       .ok_or(DoubleError::EmptyVec)
       .and_then(|s| s.parse::<i32>()
            // ここでも新しいエラー型へとアップデートさせる。
            .map_err(DoubleError::Parse) // <=================ここ
            .map(|i| 2 * i))
}

検証

ドキュメントの型通りにmap_errを実装するなら

fn double_first(vec: Vec<&str>) -> Result<i32> {
    vec.first()
        .ok_or(DoubleError::EmptyVec)
        .and_then(
            |s| s.parse::<i32>()
            .map_err(|e|DoubleError::Parse(e))
            .map(|i| 2 * i)
        )
}

になると思ったし、これで問題なく動きます。
enumのタプル構造体を渡してもどちらでも動くのでもしかしてmap_errだけでなく、他でも動作するのではと思い、以下サンプルを試してみました。

#[derive(Debug)]
enum Osaka {
    Takoyaki(i32),
}

fn takoyaki<F>(f: F) where
    F: FnOnce(i32) -> Osaka
{
    println!("{:?}", f(7));
}

fn main() {
    takoyaki(Osaka::Takoyaki);
}

// 出力:Takoyaki(7)

できてしまいました・・・

検索すると出てきました
列挙型
以下抜粋

列挙型のコンストラクターは関数のようにも使うことができます

試しに意図的にエラーを起こして、エラーメッセージを読んでみます。

fn main() {
    println!("{}", Osaka::Takoyaki); // エラー
}

// fn(i32) -> Osaka {Osaka::Takoyaki}` cannot be formatted with the default formatter

すると型が判明し、fn(i32) -> Osakaだということがわかりました。

なんかこんなのもありました。
Rust 1.40.0でtuple構造体とenum variantのコンストラクタがconst fnになる - Qiita


感想

すっ飛ばそうと思ったけど、ちゃんと調べてよかった!スッキリ!