R
RUSTY YELLOW PAGES

.. / ..= / ...Punctuation


Explanation

Three related tokens, disambiguated by position, cover ranges in Rust:

.. — rest-of-pattern. Inside a struct, tuple, or slice pattern, .. stands for "the remaining fields/elements, which this pattern doesn't name." Point { x, .. } matches any Point and binds only x, ignoring every other field; [first, ..] matches a slice of any length ≥ 1 and binds only its first element. A name placed before @ and after .. inside a slice pattern ([first, rest @ ..]) binds the ignored middle as a subslice instead of discarding it entirely. See destructuring for the broader pattern-matching context this fits into.

.. — exclusive range expression. Outside pattern position, a..b builds a Range value covering a up to but not including b, used most often to drive a for loop or as an index into [ ] for slicing. The endpoints are optional: a.. (RangeFrom, unbounded above), ..b (RangeTo, unbounded below), and .. alone (RangeFull, the whole collection, as in &data[..]).

..= — inclusive range. a..=b includes b itself, both as an expression (RangeInclusive) and, unlike bare .., also as a pattern: 1..=5 => ... in a match arm matches any value from 1 through 5 inclusive. Plain .. is not legal in pattern position for a bounded range — only ..= is.

... — deprecated inclusive range pattern. Before ..= was stabilized (Rust 1.26), inclusive range patterns were written 1...5. That syntax is obsolete: it has no expression-position meaning at all (there was never a 1...5 range expression), and in pattern position current Rust rejects it outright rather than merely warning. There is no reason to write it in new code — ..= is its direct, and only, replacement.

Usage examples

Iterating over an exclusive range

for i in 0..5 { // <- `..` exclusive range: yields 0, 1, 2, 3, 4
    println!("{i}");
}

Working with collections

Averaging a fixed-size window of recent sensor readings needs a slice of exactly that window — an exclusive range expression is the index that picks it out.

fn moving_average(readings: &[f64], start: usize, window: usize) -> f64 {
    let end = start + window;
    let slice = &readings[start..end]; // <- `..` as an exclusive range used to index a slice
    slice.iter().sum::<f64>() / slice.len() as f64
}

let readings = [21.0, 21.4, 21.6, 22.1, 21.9];
println!("{:.2}", moving_average(&readings, 1, 3));

start..end reads as "from start, up to but not including end," matching how slice.len() and index arithmetic already work in Rust — the std docs on slice indexing use this same half-open convention throughout the standard library.

Branching on data (pattern matching)

Summarizing a list of scores needs different handling for an empty list, a single score, and everything else — .. in a slice pattern matches "the rest," however many elements that turns out to be.

fn summarize(scores: &[u32]) -> String {
    match scores {
        [] => "no scores".to_string(),
        [only] => format!("single score: {only}"),
        [first, ..] => {
            // <- `..` matches the remaining elements without naming or counting them
            format!("first score: {first}, {} more", scores.len() - 1)
        }
    }
}

println!("{}", summarize(&[92, 87, 95]));

A slice pattern with .. is exhaustive over "any length ≥ 1" in one arm, instead of writing out a separate arm per possible length — the Rust Reference on slice patterns documents .. as matching zero or more elements at that position.

Numeric computation

Assigning a letter grade from a numeric score is a direct fit for inclusive range patterns, since a grade boundary (100) belongs to the band below it, not the one above.

fn letter_grade(score: u8) -> char {
    match score {
        90..=100 => 'A', // <- `..=`: 100 is included in this arm
        80..=89 => 'B',
        70..=79 => 'C',
        _ => 'F',
    }
}

println!("{}", letter_grade(90));

Writing 90..=100 states the boundary directly, where a bare 90..101 (or two overlapping-looking 90..100 arms) would leave a reader to double-check whether the endpoint is included — the Rust Reference on range patterns requires ..=, not .., for exactly this inclusive case.

Explanation

Embedded support: Full

../..=/... mean exactly the same thing under #![no_std] — allocator-free, compiling down to a plain counter and comparison. One nuance worth flagging: "slicing bits out of a register" is a common phrase in embedded code, but that isn't what the .. index form does — pulling bits 4 through 7 out of a u32 register value is a shift-and-mask operation (or a bitflags/bitfield crate), not a range expression. What .. genuinely indexes is a buffer — a fixed-size sample or DMA buffer — which is exactly as common in firmware as it is in hosted code.

Usage examples

Slicing a fixed sample buffer

fn moving_average(samples: &[u16; 32], start: usize, window: usize) -> u32 {
    let window_slice = &samples[start..start + window]; // <- `..` exclusive range slices the buffer
    window_slice.iter().map(|&s| s as u32).sum::<u32>() / window as u32
}

Validating a baud-rate divisor with an inclusive range pattern

fn configure_baud(divisor: u16) -> Result<(), &'static str> {
    match divisor {
        1..=0xFFFF => Ok(()), // <- `..=` inclusive range pattern: 0 is the one invalid divisor
        _ => Err("divisor must be nonzero"),
    }
}