Skip to content

← Ground Rules: Values, Types, Control Flow step 17 of 24

Easy Primitives

Ranges: .., ..=, and range patterns

Rewrite the starter's comparisons using ranges, keeping the same labels.

pub fn bucket(scores: &[i32]) -> Vec<String>
Score Label
0 through 33, inclusive "low"
34 through 66, inclusive "mid"
67 through 100, inclusive "high"
Anything else "invalid"

Return one label per score, in input order. Keep duplicates, and return an empty vector for an empty slice. Every i32 is a valid input; values outside 0..=100 receive the label "invalid" rather than causing an error.

For example, [33, 34, 66, 67, -1, 101] produces ["low", "mid", "mid", "high", "invalid", "invalid"].

The starter produces the right labels but fails the Clippy gate on its three manually written interval checks. A match with range patterns is a clear way to express these bands. Range .contains() checks also work.

Inclusive and exclusive endpoints

a..b includes the start and excludes the end. a..=b includes both ends. For integers, 0..5 yields 0, 1, 2, 3, 4, while 0..=5 also yields 5.

Half-open ranges fit indexing: 0..values.len() describes the valid indices of a slice. Inclusive ranges are useful for bands such as "scores from zero through thirty-three."

This complete example uses range values for iteration, membership and slicing:

fn main() {
    let exclusive: Vec<_> = (0..5).collect();
    let inclusive: Vec<_> = (0..=5).collect();
    assert_eq!(exclusive, vec![0, 1, 2, 3, 4]);
    assert_eq!(inclusive, vec![0, 1, 2, 3, 4, 5]);

    assert!((0..=100).contains(&67));
    assert!((..5).contains(&-10));

    let values = [10, 20, 30, 40, 50];
    assert_eq!(&values[1..4], &[20, 30, 40]);
    assert_eq!(&values[..2], &[10, 20]);
}

The available operations depend on the range type and its endpoints. ..5 has no starting point, so it cannot be iterated directly, but it can test membership or describe a slice boundary. Slicing also requires valid indices: an end beyond the slice length or a start after the end panics.

Matching a value against a range

A range pattern tests a value without iterating over the interval. This example classifies a smaller set of values:

fn label(value: i32) -> &'static str {
    match value {
        0..=2 => "first band",
        3..=5 => "second band",
        _ => "outside",
    }
}

fn main() {
    assert_eq!(label(2), "first band");
    assert_eq!(label(3), "second band");
    assert_eq!(label(-1), "outside");
}

match must cover every possible input. The _ arm handles everything not matched by an earlier arm. Apply this shape to the three score bands. Exclusive range patterns such as 0..3 are also supported by the toolchain; choose the notation that makes the endpoints clearest.

Understanding the lint

The starter's s >= 0 && s <= 33 spells out an inclusive interval. Clippy's manual_range_contains suggests (0..=33).contains(&s). Equivalent checks appear for the other two bands. Warnings fail the gate because it runs with -D warnings.

Either use those membership checks or replace the chain with range-pattern arms. Both approaches express the intended boundaries directly.

Reversed ranges and integer limits

Iterating an integer range with its start above its end yields no values; it does not count backwards. To visit 4, 3, 2, 1, 0, use (0..5).rev(). This differs from slicing with reversed bounds, which panics.

An inclusive range can reach an integer type's maximum without calculating a larger endpoint:

fn main() {
    let descending: Vec<_> = (0..5).rev().collect();
    assert_eq!(descending, vec![4, 3, 2, 1, 0]);
    assert_eq!((0_u8..=u8::MAX).count(), 256);
    assert_eq!((0_u8..u8::MAX).count(), 255);
}

Both maximum-bound forms are valid. The inclusive one contains 255; the exclusive one stops at 254. Do not replace the inclusive endpoint with u8::MAX + 1, which cannot be represented as a u8.

Submissions must compile, pass the tests and pass clippy -D warnings.