鴨川のはりねずみ

Option<&String>とOption<&str>

昨日の Rust コードの中で, &Option<String>Option<&str> と比較したいシチュエーションがありました. コマンドライン引数のパースの際に「引数がない, または引数が --version である」にマッチしたかったのです. 最初に書いたコードはこんな感じでした.

let args: Vec<_> = std::env::args().collect();
let arg: Option<&String> = args.get(1);

match arg {
    Some("--version") | None => {
        /* 省略 */
    },
    /* 以下省略 */
}

しかしこれはコンパイルが通りません. argOption<&String> であるのに対して, マッチの一つ目 Some("--version")Option<&str> であり, 型が合っていません. これを見て, &StringDeref<Target=str> ですから, as_deref を使えば良いだろうと次のように書き換えました.

let args: Vec<_> = std::env::args().collect();
let arg: Option<&String> = args.get(1);

match arg.as_deref() {
    Some("--version") | None => {
        /* 省略 */
    },
    /* 以下省略 */
}

しかしやはり型が合わないという理由でコンパイルエラーでした. はて?

as_deref のドキュメントには次のコード例が掲載されています (doc より引用).

let x: Option<String> = Some("hey".to_owned());
assert_eq!(x.as_deref(), Some("hey"));

一瞬同じでは? と思いますが, よく説明を読むとこのメソッドは Option<T> または &Option<T>Option<&T::Target> に変換する, と書かれています. 今したいことは Option<&T>Option<&T::Target> に変換することであり, 微妙に違います. つまり as_deref は使えません.

&String を明示的に &str に変換する場合, String::as_str メソッドが使われます. 従って今の状況では as_deref ではなくこのメソッドで map すれば解決します.

let args: Vec<_> = std::env::args().collect();
let arg: Option<&String> = args.get(1);

match arg.map(String::as_str) {
    Some("--version") | None => {
        /* 省略 */
    },
    /* 以下省略 */
}

あるいは汎用的に Deref を呼ぶのでも構いません.

use std::ops::Deref;

let args: Vec<_> = std::env::args().collect();
let arg: Option<&String> = args.get(1);

match arg.map(Deref::deref) {
    Some("--version") | None => {
        /* 省略 */
    },
    /* 以下省略 */
}

あるいは今の場合, arg: Option<&String> が所有権を持っていないことが問題で, 所有権を持っていれば as_deref できます. つまり最初に引数のリスト args を確保するのではなく

let arg = std::env::args().nth(1);

match arg.as_deref() {
    Some("--version") | None => {
        /* 省略 */
    },
    /* 以下省略 */
}

とする手もあります.