How to use the range syntax
Short answer: .. excludes its end value, ..= includes it. 0..3 is 0, 1, 2 and 0..=3 is
0, 1, 2, 3. They’re also two different types, Range<i32> and RangeInclusive<i32>, so one can’t
stand in for the other.
The end is the only difference
Section titled “The end is the only difference”In src/main.rs:
fn main() { for i in 0..3 { println!("{i}"); } println!("--"); for i in 0..=3 { println!("{i}"); }}cargo run prints:
012--0123Same start, same end value written in the same place, one character apart in the middle. .. is
half-open: its end is the first value that isn’t included. ..= is closed, so its end is the last value
you get.
A range is a value
Section titled “A range is a value”Neither form is loop syntax. Both build a value you can hold on to:
fn main() { let exclusive = 2..6; let inclusive = 2..=6;
println!("exclusive: {} up to {}", exclusive.start, exclusive.end); println!("inclusive: {} up to {}", inclusive.start(), inclusive.end()); println!("{} items / {} items", (2..6).count(), (2..=6).count()); println!("holds 6? {} / {}", exclusive.contains(&6), inclusive.contains(&6));}exclusive: 2 up to 6inclusive: 2 up to 64 items / 5 itemsholds 6? false / trueexclusive is a Range<i32>, and it keeps its ends in the fields start and end. inclusive is a
RangeInclusive<i32>, which keeps them private: start() and end() are methods there. Getting that
wrong is a compile error rather than something silent:
fn main() { let inclusive = 2..=6; println!("{}", inclusive.end);}cargo build prints:
error[E0616]: field `end` of struct `std::ops::RangeInclusive` is private --> src/main.rs:3:30 |3 | println!("{}", inclusive.end); | ^^^ private field |help: a method `end` also exists, call it with parentheses |3 | println!("{}", inclusive.end()); | ++
For more information about this error, try `rustc --explain E0616`.Both forms answer count(), because both are iterators, and both answer contains().
Two types, not one
Section titled “Two types, not one”The names aren’t interchangeable, and not only in a signature you write yourself:
fn main() { let r: std::ops::Range<i32> = 2..=6; println!("{r:?}");}cargo build prints:
error[E0308]: mismatched types --> src/main.rs:2:35 |2 | let r: std::ops::Range<i32> = 2..=6; | -------------------- ^^^^^ expected `Range<i32>`, found `RangeInclusive<{integer}>` | | | expected due to this | = note: expected struct `std::ops::Range<i32>` found struct `std::ops::RangeInclusive<{integer}>`
For more information about this error, try `rustc --explain E0308`.A function that takes a Range<i32> won’t accept 2..=6. Swapping .. for ..= changes the type of
the expression, so it isn’t a flag you can flip at the call site. The {integer} in the note is rustc’s
stand-in for a literal whose type isn’t pinned down yet; annotate r as RangeInclusive<i32> and it
goes away.
Why 0..len is the common one
Section titled “Why 0..len is the common one”Half-open ranges line up with lengths, because the last valid index is one below the length:
fn main() { let values = vec![10, 20, 30]; let mut total = 0; for i in 0..values.len() { total += values[i]; } println!("{total} over {} items", values.len());}60 over 3 itemsvalues.len() is 3 and the last index is 2, so 0..values.len() visits every item with nothing to
adjust. The inclusive version of the same loop would have to say 0..=values.len() - 1, and that
subtraction panics on an empty collection — attempt to subtract with overflow, because a usize won’t
go below zero. The half-open form needs no correction: 0..0 is simply empty.
Empty and backwards
Section titled “Empty and backwards”fn main() { let mut visited = 0; for _ in 3..3 { visited += 1; } println!("3..3 is {visited}"); for _ in 3..0 { visited += 1; } println!("3..0 is {visited}"); for _ in 3..=3 { visited += 1; } println!("3..=3 is {visited}"); let down: Vec<i32> = (1..=3).rev().collect(); println!("{down:?}");}3..3 is 03..0 is 03..=3 is 1[3, 2, 1]3..3 holds nothing, since 3 is excluded. 3..=3 holds exactly one value. 3..0 is empty as well,
not an error: a range doesn’t run backwards, so the loop stops before it starts, and a countdown needs
.rev() on an upward range.
Slicing is less forgiving. A backwards range used as an index panics — slice index starts at 2 but ends at 0 — and so does an end past the length: &items[0..5] on four items reports range end index 5 out of range for slice of length 4. The same range that quietly does nothing in a loop is a hard failure in
an index.
Leaving an end off
Section titled “Leaving an end off”Drop one end and you still have a range. That’s why slicing looks the way it does:
fn main() { let items = ["a", "b", "c", "d"]; println!("{:?}", &items[1..3]); println!("{:?}", &items[..2]); println!("{:?}", &items[2..]); println!("{:?}", &items[0..items.len()]); println!("{}", items[..].len());}["b", "c"]["a", "b"]["c", "d"]["a", "b", "c", "d"]4&items[..2] runs from the beginning, &items[2..] runs to the end, and .. on its own is everything,
so &items[0..items.len()] and &items[..] are the same slice. Same half-open rule, with a missing end
meaning “no end”: 0.. is that idea in a loop, a range that starts at zero and never names where to
stop.
Related
Section titled “Related”- Why do both branches of an if need the same type? — the article before this one
- How to return a value from a function — the tail expression that gives a block its value
- The bits that trip everyone up — the rest of this chapter
- Next in this chapter: breaking out of two nested loops at once