コンテンツにスキップ

Rustのif let / while let

これまで Option や Result を扱うのに match を使ってきました。でも実際には、気にしたいパターンは1つだけで、ほかはすべて無視したい、ということがよくあります。何もしないためだけに _ => {} のアームを書いて match を完成させることもできますが、やりたいことに対して大げさです。そんなときの近道が if let です。

matchを全部書くと大げさになる例

Section titled “matchを全部書くと大げさになる例”
fn main() {
let age: Option<i32> = Some(30);
match age {
Some(value) => println!("年齢は{value}歳です"),
_ => {}, // このケースは実は気にしていない
}
}

この _ => {} は、「すべてのケースを処理する」という match のルールを満たすためだけにあって、何もしていません。Some のときだけ何かしたいのなら、このコードは考えていることに比べて長すぎます。

fn main() {
let age: Option<i32> = Some(30);
if let Some(value) = age {
println!("年齢は{value}歳です");
}
}

実行結果:

年齢は30歳です
▶ 実際に試してみよう

これは「もし age が Some(value) というパターンに当てはまるなら、中身を value に入れて、このブロックを実行する」と読みます。age が None なら、ブロックは飛ばされるだけです。エラーにもならず、何も表示されません。

ふつうの if と同じように、if let にも else を付けられます。

fn main() {
let age: Option<i32> = None;
if let Some(value) = age {
println!("年齢は{value}歳です");
} else {
println!("年齢が指定されていません");
}
}
▶ 実際に試してみよう

Resultのページで学んだ Result でも、同じように使えます。

fn main() {
let input = "42";
if let Ok(number) = input.parse::<i32>() {
println!("変換結果: {number}");
} else {
println!("数値に変換できませんでした");
}
}
▶ 実際に試してみよう

if に else if があるように、if let の後ろにも else if let を続けられます。Rustはパターンを順番に試し、最初に当てはまったブロックを実行します。値を探す場所がいくつかあって、優先順位が決まっているときに便利です。

fn main() {
let nickname: Option<&str> = None;
let full_name: Option<&str> = Some("フェリス・クラブ");
if let Some(nick) = nickname {
println!("やあ、{nick}!");
} else if let Some(name) = full_name {
println!("こんにちは、{name}さん。");
} else {
println!("こんにちは、どなたか存じませんが。");
}
}

実行結果:

こんにちは、フェリス・クラブさん。
▶ 実際に試してみよう

nickname は None なので、最初のブロックは飛ばされます。full_name は Some(name) に当てはまるので、そのブロックが実行され、最後の else にはたどり着きません。else if is_admin { ... } のような、ふつうの条件をチェーンのどこかに混ぜることもできます。

それぞれの変数は、自分のブロックの中にしか存在しません。nick は2つ目のブロックでは使えませんし、name もチェーンが終わった後では使えません。取り出した値をその後のコード全体で使いたいなら、パターンマッチングのページにあるlet-elseを見てみましょう。

while let は同じ考え方をループに当てはめたものです。値がパターンに当てはまる間、ループを続けます。コレクションの中身を1つずつ取り出していくときに特に便利です。

fn main() {
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{top}");
}
}

実行結果:

3
2
1
▶ 実際に試してみよう

.pop() は、要素が残っている間は Some(value) を返し、ベクタが空になると None を返します。None になった時点で、while let がループを自動的に止めてくれます。長さを自分でチェックする必要はありません。

複数のケースをそれぞれ違うやり方で処理したいなら、match を使いましょう。気にしたいパターンが本当に1つだけで、ほかはすべて無視してよいなら、if let の出番です。if let は match の代わりになるものではありません。「1つのアームだけ気にしたい」というよくある場面を、短く書くための方法です。

let mut numbers = vec![4, 8, 15]; と、0 から始まる合計 total を用意します。while let と .pop() を使って末尾から数を1つずつ取り出し、それを total に足して、取り出した数と新しい合計を表示してください。ヒント:total には mut が必要です。total += n で足せます。

解答を見る
fn main() {
let mut numbers = vec![4, 8, 15];
let mut total = 0;
while let Some(n) = numbers.pop() {
total += n;
println!("{n}を取り出しました。合計は{total}です");
}
}

実行結果:

15を取り出しました。合計は15です
8を取り出しました。合計は23です
4を取り出しました。合計は27です
▶ 実際に試してみよう

.pop() は末尾から取り出すので、15が最初に出てきます。

2. 文字列から年齢をチェックする

Section titled “2. 文字列から年齢をチェックする”

let input = "17"; を用意します。u32 に変換できたら、その年齢が成人(18歳以上)か未成年かを表示してください。変換できなければ、数値ではないと表示します。input を "abc" に変えても試してみましょう。ヒント:if let のブロックの中に、ふつうの if / else を書きます。

解答を見る
fn main() {
let input = "17";
if let Ok(age) = input.parse::<u32>() {
if age >= 18 {
println!("{age}歳は成人です");
} else {
println!("{age}歳は未成年です");
}
} else {
println!("「{input}」は数値ではありません");
}
}

実行結果:

17歳は未成年です
▶ 実際に試してみよう

input = "abc" にすると変換に失敗し、外側の else が実行されて 「abc」は数値ではありません と表示されます。

  • if let パターン = 値 { ... } は、値 が パターン に当てはまるときだけブロックを実行します。それ以外は何も言わずに飛ばされます。
  • ふつうの if と同じく、else を付けると当てはまらないケースも処理できます。else if let で複数のパターンをつなげることもできます。
  • while let パターン = 値 { ... } は、値がパターンに当てはまり続ける間ループします。.pop() でコレクションを空にしていくときに便利です。
  • 気にするパターンが1つだけなら if let / while let を、いくつものケースを別々に処理したいなら match を使いましょう。

理解度チェック

1. if let Some(value) = age を実行したとき、age が None だとどうなりますか?

2. if let ではなく match を使うべきなのはどんなときですか?

3. while let Some(top) = stack.pop() { ... } のループは、いつ止まりますか?

スコア: 0 / 3