範囲型

Essential
最終更新: タグ: Rust, 標準ライブラリ

範囲式は std::opsRange* 型の値を生成するリテラルだ。1..5 と書くことは Range<i32> の値を構築する糖衣構文であり、格納・渡し・イテレートできる実際の型だ。

範囲型

意味
a..bRange<T>半開区間: a ≤ n < b
a..=bRangeInclusive<T>閉区間: a ≤ n ≤ b
a..RangeFrom<T>a ≤ n(上限なし)
..bRangeTo<T>n < b(下限なし)
..=bRangeToInclusive<T>n ≤ b(下限なし)
..RangeFullすべての要素

Range<T>RangeInclusive<T> がもっとも一般的だ。Iterator を実装しているからだ。残りの RangeToRangeToInclusiveRangeFromRangeFull は主にスライスのインデックスとして使われる。

イテレータとしての範囲

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();

RangeFroma..)は永遠に上に向かってカウントするイテレータでもある。無限ループを避けるには .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..=49RangeInclusive<i32> を構築しない;純粋にコンパイル時の数値範囲に対する構文的チェックだ。

まとめ

  • a..bRange<T>(半開区間)を生成し、a..=bRangeInclusive<T>(閉区間)を生成する。
  • RangeRangeInclusive は整数型に対して Iterator を実装しており、for ループやイテレータアダプタで直接使える。
  • ..ba....=b.. は主にスライスインデックスとして使われ、コピーなしで連続した部分範囲を選択する。
  • マッチアームのパターン a..=b は範囲のように見えるが別の構文であり、Range オブジェクトを構築するのではなく値をテストする。