Rustで範囲を表すRange(..)の種類と使い方
Rustでは、..で範囲を表す構造体Rangeを生成できる。スライスの範囲を指定したり、イテレータとしてforループで使ったり、連番のベクタVecに変換したりできる。
範囲を表す構造体には、他にもRangeInclusive(..=)などがある。
Rangeなどの範囲を表す構造体まとめ(スライスの例)
範囲を表す構造体は以下の6種類。
- Range:
start..end - RangeInclusive:
start..=end - RangeTo:
..end - RangeToInclusive:
..=end - RangeFrom:
start.. - RangeFull:
..
スライスの範囲として指定した場合の結果を以下に示す。
=がつくとendが含まれ、startを省略すると先頭から、endを省略すると末尾までが範囲になる。
let a = [0, 10, 20, 30, 40];
assert_eq!(&a[1..3], [ 10, 20 ]); // Range
assert_eq!(&a[1..=3], [ 10, 20, 30 ]); // RangeInclusive
assert_eq!(&a[..3], [0, 10, 20 ]); // RangeTo
assert_eq!(&a[..=3], [0, 10, 20, 30 ]); // RangeToInclusive
assert_eq!(&a[1..], [ 10, 20, 30, 40]); // RangeFrom
assert_eq!(&a[..], [0, 10, 20, 30, 40]); // RangeFull
なお、範囲外を指定すると実行時にパニックになる。
// let x = &a[1..100];
// thread 'main' panicked at 'range end index 100 out of range for slice of length 5'
getメソッドを使うと結果をOptionで取得できる。範囲外だとNoneが返される。
assert_eq!(a.get(1..3).unwrap(), &a[1..3]);
assert_eq!(a.get(1..100), None);
Rangeなどをイテレータとして使う(forループ、Vecに変換)
RangeなどにはIteratorトレイトやDoubleEndedIteratorトレイトが実装され、イテレータとして使えるものがある。
Range,RangeInclusiveIterator,DoubleEndedIteratorが実装されている
RangeFromIteratorのみが実装されている
RangeTo,RangeToInclusive,RangeFull- どちらも実装されていない
Range, RangeInclusive
RangeとRangeInclusiveにはIteratorトレイトが実装されている。forループで使ったり、collectでベクタVecに変換したりできる。
for i in 1..4 {
println!("{i}");
}
// 1
// 2
// 3
let v: Vec<i32> = (1..4).collect();
assert_eq!(v, [1, 2, 3]);
mapと組み合わせる例。
let v: Vec<i32> = (1..4).map(|x| x * 10).collect();
assert_eq!(v, [10, 20, 30]);
ステップを指定したり降順(逆順)にする例は次の節でまとめて示す。
RangeFrom
RangeFromにもIteratorトレイトが実装されているが、終点を持たないので、breakなしのforループで使うと無限ループになってしまう。要注意。
また、そのままcollectでVecに変換すると実行時にパニックになる。Clippyを使っていれば拒絶(deny)される。
// let v: Vec<i32> = (1..).collect();
// thread 'main' panicked at 'capacity overflow'
zipで終点(個数)が決まっている相手と組み合わせると変換可能。enumerateの開始値を変更するような処理ができる。
let a = ['a', 'b', 'c'];
let v: Vec<(i32, char)> = (10..).zip(a).collect();
assert_eq!(v, [(10, 'a'), (11, 'b'), (12, 'c')]);
RangeTo, RangeToInclusive, RangeFull
RangeToやRangeToInclusive, RangeFullは始点が定められないので、Iteratorトレイトは実装されていない(できない)。イテレータとしては使えない。
// for i in ..4 {}
// error[E0277]: `std::ops::RangeTo<{integer}>` is not an iterator
// let v: Vec<i32> = (..4).collect();
// error[E0599]: `std::ops::RangeTo<{integer}>` is not an iterator
Range, RangeInclusiveから連番のベクタを生成
ステップ: step_by
step_byでステップを指定できる。
let v: Vec<i32> = (1..10).step_by(2).collect();
assert_eq!(v, [1, 3, 5, 7, 9]);
降順(逆順): rev
RangeとRangeInclusiveにはDoubleEndedIteratorトレイトも実装されているので、revで反転できる。
let v: Vec<i32> = (1..4).rev().collect();
assert_eq!(v, [3, 2, 1]);
step_byとrevの組み合わせ
step_byとrevを組み合わせる例は以下の通り。当然ながら、順番によって結果が変わる。
let v: Vec<i32> = (1..10).step_by(3).rev().collect();
assert_eq!(v, [7, 4, 1]);
let v: Vec<i32> = (1..10).rev().step_by(3).collect();
assert_eq!(v, [9, 6, 3]);
// let v: Vec<i32> = (1..=10).step_by(3).rev().collect();
// error[E0277]: the trait bound `std::ops::RangeInclusive<i32>: std::iter::ExactSizeIterator` is not satisfied
let v: Vec<i32> = (1..=10).rev().step_by(3).collect();
assert_eq!(v, [10, 7, 4, 1]);
RangeInclusive(start..=end)はstep_by().rev()の順番だとエラーになるので注意(バージョン1.66時点)。start..=endではなくstart..=(end+1)を使えばよい。
エラーメッセージにあるように、RangeInclusiveがExactSizeIteratorトレイトを実装していないのが原因。
- Can't use DoubleEndedIterator for inclusive ranges · Issue #66195 · rust-lang/rust
- RangeInclusive<usize> shouldn't impl ExactSizeIterator · Issue #36386 · rust-lang/rust
空のRange, RangeInclusive
Rangeにおいてstart >= end、RangeInclusiveにおいてstart > endだと、空になる。collectでベクタVecに変換しても空。
let start = 5;
let end = 1;
let r = start..end;
assert!(r.is_empty());
let v: Vec<i32> = r.collect();
assert!(v.is_empty());
降順にしたい場合は上述のrevを使う。
なお、空のRange, RangeInclusiveをスライスの範囲として指定すると実行時にパニックになる。getメソッドだとNoneが返される。
let start: usize = 5;
let end: usize = 1;
let r = start..end;
let a = [0, 10, 20, 30, 40];
// let x = &a[r];
// thread 'main' panicked at 'slice index starts at 5 but ends at 1'
assert_eq!(a.get(r), None);