範囲型
Essential前提知識
範囲式は std::ops の Range* 型の値を生成するリテラルだ。1..5 と書くことは Range<i32> の値を構築する糖衣構文であり、格納・渡し・イテレートできる実際の型だ。
範囲型
| 式 | 型 | 意味 |
|---|---|---|
a..b | Range<T> | 半開区間: a ≤ n < b |
a..=b | RangeInclusive<T> | 閉区間: a ≤ n ≤ b |
a.. | RangeFrom<T> | a ≤ n(上限なし) |
..b | RangeTo<T> | n < b(下限なし) |
..=b | RangeToInclusive<T> | n ≤ b(下限なし) |
.. | RangeFull | すべての要素 |
Range<T> と RangeInclusive<T> がもっとも一般的だ。Iterator を実装しているからだ。残りの RangeTo・RangeToInclusive・RangeFrom・RangeFull は主にスライスのインデックスとして使われる。
イテレータとしての範囲
Range<T> と RangeInclusive<T> は整数型に対して Iterator を実装しているため、for ループで直接使える:
for i in 0..5 {
println!("{i}"); // 0, 1, 2, 3, 4
}
for i in 0..=5 {
println!("{i}"); // 0, 1, 2, 3, 4, 5
}
範囲は Iterator なので、任意のイテレータアダプタを呼べる:
let squares: Vec<i32> = (1..=5).map(|n| n * n).collect(); // [1, 4, 9, 16, 25]
let evens: Vec<i32> = (0..20).filter(|n| n % 2 == 0).collect();
RangeFrom(a..)は永遠に上に向かってカウントするイテレータでもある。無限ループを避けるには .take(n) と組み合わせる:
let first_five: Vec<i32> = (10..).take(5).collect(); // [10, 11, 12, 13, 14]
スライスインデックスとしての範囲
任意のスライスや Vec を範囲でインデックスすると、連続した部分範囲を選択できる。コピーなしでスライス参照が生成される:
let v = vec![10, 20, 30, 40, 50];
let a: &[i32] = &v[1..4]; // [20, 30, 40] — Range
let b: &[i32] = &v[..3]; // [10, 20, 30] — RangeTo
let c: &[i32] = &v[3..]; // [40, 50] — RangeFrom
let d: &[i32] = &v[..]; // [10, 20, 30, 40, 50] — RangeFull
let e: &[i32] = &v[1..=3]; // [20, 30, 40] — RangeInclusive
境界値は実行時にチェックされ、範囲外の場合はパニックする。
match における範囲
match における範囲パターンは同じ ..= 構文を使うが、Range* 型とは別物だ — パターン言語の機能であり、値ではない:
let label = match score {
0..=49 => "fail",
50..=69 => "pass",
70..=89 => "merit",
_ => "distinction",
};
マッチパターン 0..=49 は RangeInclusive<i32> を構築しない;純粋にコンパイル時の数値範囲に対する構文的チェックだ。
まとめ
a..bはRange<T>(半開区間)を生成し、a..=bはRangeInclusive<T>(閉区間)を生成する。RangeとRangeInclusiveは整数型に対してIteratorを実装しており、forループやイテレータアダプタで直接使える。..b・a..・..=b・..は主にスライスインデックスとして使われ、コピーなしで連続した部分範囲を選択する。- マッチアームのパターン
a..=bは範囲のように見えるが別の構文であり、Rangeオブジェクトを構築するのではなく値をテストする。