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 => {
/* 省略 */
},
/* 以下省略 */
}
しかしこれはコンパイルが通りません. arg は Option<&String> であるのに対して,
マッチの一つ目 Some("--version") は Option<&str> であり, 型が合っていません.
これを見て, &String は Deref<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 => {
/* 省略 */
},
/* 以下省略 */
}
とする手もあります.