null 代わりの Option<T>
Rust には Option<T> という型があります。だいたいこういう定義です。
enum Option<T> {
None,
Some(T),
}
Rust は暗黙に null を許容する参照型……のようなものはありません。代わりに明示的に Option<T> 型を使用します。値がないことを None 、値があることを Some で示します。 (いきなり余談ですが std::ptr::null があるので null がないはウソになりますね)
Option<T> は標準ライブラリを含めて広く使われている型なので、 Rust をはじめるとすぐに触れることになります。単純な構造ではあるのですが、ぼくの所属するドクターメイト株式会社でのメンバーの経過を見るに、慣れて使いこなすまでには意外と時間がかかっているように思います。
基本的なところから順に押さえるためにも、改めて書いてみます。
個人の意見: 暗黙に null を許容されるのが嫌い
また余談ですが、ぼくは暗黙に null を許容されるのが嫌いです。
「もしかしたら T 型の変数の値が null かもしれない」と不安になったり、「何かの間違いで Optional<T> 型の変数の値が null かもしれない」とおびえたりします。そういうことに不安を感じたり、おびえたりするのが嫌です。ぼくは何が嫌いかは何が好きかと同じくらい自分を語る上で大切だと思っています。
Rust で null に相当する None は Option<T> 型の値にしか出てきません。安心です。
基本的な使い方
Option<T> は、たとえば次のように構築できます。
let o1: Option<i32> = None;
let o2: Option<i32> = Some(123);
Option<T> は、たとえば次のように分解できます。
let o: Option<i32> = Some(123);
match o {
None => println!("None!"),
Some(v) => println!("Some({})", v),
}
match は前回の「 if より match 」 でも触れていますね。
今回はあえて if (bool) を使う形にもしてみます。
bool への変換の例
is_none / is_some
単に None か Some かを判断したいなら is_none と is_some を使えます。
let o1: Option<i32> = None;
assert!(o1.is_none());
let o2: Option<i32> = Some(123);
assert!(o2.is_some());
TypeScript のような Narrowing があるわけではないので、 is_some を呼び出したからといって値を取り出すには他のメソッドの呼び出しが必要です。
let o: Option<i32> = Some(123);
if o.is_some() {
// unwrap などを呼び出さないと 123 を取り出せない
assert_eq!(o.unwrap(), 123);
}
is_some_and / is_none_or
中身を考慮しての判定が必要なだけなら unwrap を避けて is_some_and も使えます。
let o: Option<i32> = Some(4);
assert!(o.is_some_and(|n| n % 2 == 0));
類似の is_none_or もあります。
ドクターメイト株式会社での運用では……
ドクターメイト株式会社での運用では「 if より match 」の影響からか、 is_some や is_some_and などの使用頻度はそこまで高くないです。必要に応じて使っていきたいところです。
おわりに
他にも変換はいろいろあります。分解することなく Option<T> の中身だけを変換したり、さきほどの unwrap のような中身の取り出しもその他の変換です。こういった変換については次回改めて書こうと思います。
追記: 次回『 Option<T> のよく使うメソッドを書いてみる』を書きました。
Discussion