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
,RangeInclusive
Iterator
,DoubleEndedIterator
が実装されている
RangeFrom
Iterator
のみが実装されている
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);