RFC 2591: exhaustive-integer-pattern-matching

lang (patterns | primitive | exhaustiveness)

Summary

Extend Rust's pattern matching exhaustiveness checks to cover the integer types: u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize and char.

fn matcher_full(x: u8) {
  match x { // ok
    0 ..= 31 => { /* ... */ }
    32 => { /* ... */ }
    33 ..= 255 => { /* ... */ }
  }
}

fn matcher_incomplete(x: u8) {
  match x { //~ ERROR: non-exhaustive patterns: `32u8..=255u8` not covered
    0 ..= 31 => { /* ... */ }
  }
}

Motivation

This is viewed essentially as a bug fix: other than the implementational challenges, there is no reason not to perform correct exhaustiveness checking on integer patterns, especially as range patterns are permitted, making it very straightforward to provide patterns covering every single integer.

This change will mean that Rust correctly performs exhaustiveness checking on all the types that currently compose its type system.

This feature has already been implemented behind the feature flag exhaustive_integer_patterns, so this RFC is viewed as a motion to stabilise the feature.

Guide-level explanation

Exhaustive pattern matching works for integer types, just like any other type. In addition, missing ranges of integers will be reported as errors.

fn matcher_full(x: u8) {
  match x { // ok
    0 ..= 31 => { /* ... */ }
    32 => { /* ... */ }
    33 ..= 255 => { /* ... */ }
  }
}

fn matcher_incomplete(x: u8) {
  match x { //~ ERROR: non-exhaustive patterns: `32u8..=255u8` not covered
    0 ..= 31 => { /* ... */ }
  }
}

Specifically, for non-char integer types, the entire range of values from {integer}::MIN to {integer}::MAX are considered valid constructors. For char, the Unicode Scalar Value (USV) ranges (\u{0000}..=\u{D7FF} and \u{E000}..=\u{10FFFF}) are considered valid constructors.

More examples may be found in the file of test cases.

Note that guarded arms are ignored for the purpose of exhaustiveness checks, just like with any other type (i.e. arms with if conditions are always considered fallible and aren't considered to cover any possibilities).

Reference-level explanation

The implementation of this features uses interval arithmetic and an extension of the pattern matching exhaustiveness checks as described in this paper.

This feature has already been implemented, so the code there may be used for further reference. The source contains detailed comments about the implementation.

For usize and isize, no assumptions about the maximimum value are permitted. To exhaustively match on either pointer-size integer type a wildcard pattern (_) must be used (or if open-ended range patterns are added, ranges must be open ended [e.g. 0..]). An unstable feature precise_pointer_size_matching will be added to permit matching exactly on pointer-size integer types.

Drawbacks

There is no reason not to do this: it fixes a limitation of the existing pattern exhaustiveness checks.

Rationale and alternatives

This is a straightforward extension of the existing exhaustiveness checks. This is the only sensible design for the feature.

Prior art

As far as the author is unaware, Rust is the first language to support exhaustive integer pattern matching. At the time of writing, Swift and OCaml, two languages for which this feature could also make sense, do not implement this extension. This is likely because the feature is not simple to implement and the usefulness of this feature appears in specific domains.

Unresolved questions

This feature is already implemented and appears to meet expectations for such a feature, as there have been no issues brought up about the implementation or design.

Future possibilities

Having added exhaustive pattern matching for integers, all types in Rust for which exhaustive matching is sensible are matched exhaustively. We should aim to ensure this remains the case. However, at present, exhaustive pattern matching in Rust is viewed complete.