Ranges

#234 Jul 2026

234. core::range::Range — A Range That's Copy, So You Can Store and Reuse It

The classic 0..3 isn’t Copy — it is the iterator, so it gets consumed and can’t live in a Copy struct. Rust 1.96 stabilises core::range::Range, which is Copy and iterates through IntoIterator instead.

Why the old Range fights you

std::ops::Range implements Iterator directly. That’s convenient in a for loop, but it means the range is mutable iterator state, so it can’t be Copy:

1
2
3
let r = 0..3;
let total: i32 = r.sum();   // moves r
// let n = r.count();       // error: use of moved value: r

It also can’t sit inside a Copy type, which is annoying when you want a lightweight span:

1
2
#[derive(Clone, Copy)]        // error: the trait Copy is not
struct Span(std::ops::Range<usize>);  // implemented for Range<usize>

The new core::range::Range is Copy

Stabilised in 1.96, core::range::Range implements IntoIterator rather than Iterator, which frees it to be Copy. Convert a literal range into it with .into():

1
2
3
4
5
6
7
use core::range::Range;

let r: Range<usize> = (0..3).into();
let total: usize = r.into_iter().sum();   // r is Copy, not moved
let count = r.into_iter().count();        // still usable
assert_eq!(total, 3);
assert_eq!(count, 3);

Now it fits in a Copy struct

Because it’s Copy, you can store a range as a cheap, copyable span and still index slices with it directly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use core::range::Range;

#[derive(Clone, Copy)]
struct Span(Range<usize>);

impl Span {
    fn of(self, s: &str) -> &str { &s[self.0] }
}

let span = Span((2..5).into());
let a = span;                 // copies, no move
assert_eq!(a.of("hello world"), "llo");
assert_eq!(span.of("hello world"), "llo");   // span still valid

No more splitting a range into separate start / end fields just to keep a struct Copy.

Heads up

0..3 syntax still produces the legacy std::ops::Range for now — the new types come in via .into() or by naming core::range::Range. A future edition will switch the syntax over. For public APIs that should accept either, take impl RangeBounds<usize>.

Stabilised in Rust 1.96 (May 2026).

92. core::range — Range Types You Can Actually Copy

Ever tried to reuse a 0..=10 range and hit “use of moved value”? Rust 1.95 stabilises core::range, a new module of range types that implement Copy.

The old pain

The classic range types in std::opsRange, RangeInclusive — are iterators themselves. That means iterating them consumes them, and they can’t be Copy:

1
2
3
4
let r = 0..=5;
let a: Vec<_> = r.clone().collect();
let b: Vec<_> = r.collect(); // consumes r
// r is gone here

Every time you want to reuse a range you reach for .clone() or rebuild it. Annoying for something that looks like a pair of integers.

The fix: core::range

Rust 1.95 adds new range types in core::range (re-exported as std::range). They’re plain data — Copy, no iterator state baked in — and you turn them into an iterator on demand with .into_iter():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use core::range::RangeInclusive;

fn main() {
    let r: RangeInclusive<i32> = (0..=5).into();

    let a: Vec<i32> = r.into_iter().collect();
    let b: Vec<i32> = r.into_iter().collect(); // r is Copy, still usable

    assert_eq!(a, vec![0, 1, 2, 3, 4, 5]);
    assert_eq!(a, b);
}

No .clone(), no rebuild. r is just two numbers behind the scenes, so copying it is free.

Passing ranges around

Because the new types are Copy, you can pass them into helpers without worrying about move semantics:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use core::range::RangeInclusive;

fn sum_range(r: RangeInclusive<i32>) -> i32 {
    r.into_iter().sum()
}

fn main() {
    let r: RangeInclusive<i32> = (1..=4).into();

    assert_eq!(sum_range(r), 10);
    assert_eq!(sum_range(r), 10); // reuse: r is Copy
}

The old std::ops::RangeInclusive would have been moved by the first call.

Field access, not method calls

The new types expose their bounds as public fields — no more .start() / .end() accessors. For RangeInclusive, the upper bound is called last (emphasising that it’s included):

1
2
3
4
5
6
7
8
use core::range::RangeInclusive;

fn main() {
    let r: RangeInclusive<i32> = (10..=20).into();

    assert_eq!(r.start, 10);
    assert_eq!(r.last, 20);
}

Exclusive Range has start and end in the same way. Either one is simple plain-old-data that you can pattern-match, destructure, or read directly.

When to reach for it

Use core::range whenever you want to store, pass, or reuse a range as a value. The old std::ops ranges are still everywhere (literal syntax, slice indexing, for loops), so there’s no rush to migrate — but for library APIs that take ranges as parameters, the new types are the friendlier choice.

Stabilised in Rust 1.95 (April 2026).