March 12, 2020·5 min read·engineeringrustpatternsdiagnostics

Rust 1.42 gives slice patterns a variable-length middle

Subslice patterns describe prefixes, suffixes, and borrowed remainders directly. The release also adds matches! and moves standard-library panic locations to their callers.

A parser often cares about the first and last elements of a sequence while treating its middle as a variable-length remainder. Fixed-length patterns cannot express that relationship directly. Rust 1.42 stabilizes subslice patterns, allowing the shape of such input to appear in the pattern itself. Length requirements and element extraction then participate in the same match.

The release announcement introduces the change alongside matches! and improved panic locations. These are useful changes to how a program describes and diagnoses its decisions. They reduce the gap between the condition a programmer means and the code or diagnostic needed to communicate it.

I like Rust's explicit treatment of data structure and ownership. Slice patterns are appealing because they express structural requirements without asking the reader to reconstruct them from separate index arithmetic. The benefit is clearest when a branch depends on several positions in the input.

A rest pattern describes a gap

Earlier slice patterns could match exact sizes. A program could distinguish an empty slice, a singleton, or a pair. To handle longer inputs it needed a fallback branch, where manual length checks and indexing could reintroduce the structure that the pattern had failed to express.

Rust 1.42 adds .. as a variable-length gap inside a slice pattern. It matches the elements not consumed by the patterns before and after it. The gap can appear at an end or between required elements. A pattern containing required first and last elements consequently requires at least two elements, even if the middle is empty.

This example separates the cases needed to describe the endpoints.

fn endpoints(values: &[u8]) -> Option<(u8, u8)> {
    match values {
        [] => None,
        [only] => Some((*only, *only)),
        [first, .., last] => Some((*first, *last)),
    }
}

The empty and singleton arms cover the cases where two distinct positions are unavailable. All remaining lengths enter the final arm. The match does not index the input after an unrelated length test. Its pattern supplies both the minimum shape and the bindings used by the result.

The language team's pattern-matching account gives a compiler-parser example with the same structure. It locates the first and last attributes when reporting an item missing after attributes. Another pattern extracts the penultimate attribute. These examples show why variable-length gaps matter in ordinary diagnostic code.

Binding the remainder preserves its role

A program can also name the gap using an @ binding. A prefix matcher can therefore retain the remainder for later processing instead of merely ignoring it. The release announcement demonstrates both a named prefix before a required final element and a named suffix after a required first element.

For a borrowed slice, a named remainder gives the code a subslice to inspect. That keeps the input relationship visible. The branch has established a required marker and identified exactly which remaining elements belong to the next operation. It does not need to allocate a replacement sequence merely to describe that partition.

The location of the gap changes the condition. A prefix pattern accepts additional trailing elements, while a suffix pattern allows additional leading elements. A middle gap permits arbitrary intervening content but still requires the named endpoints. Those distinctions are part of the match, so they remain visible when the branch body becomes longer.

The language-team account says stabilization followed more than seven years on nightly, including redesign, borrow-checker soundness repairs, and substantial exhaustiveness-checker work. The surface notation is short because the compiler carries the difficult obligations. It must reason about which lengths and bindings each arm permits.

matches! names a Boolean pattern test

Some matches produce only a Boolean. Writing a full match with one successful arm and a false fallback repeats control-flow syntax without adding another result. Rust 1.42 adds matches! for that case. It accepts an expression and a pattern and returns whether the expression matches.

fn is_positive(value: Option<i32>) -> bool {
    matches!(value, Some(number) if number > 0)
}

The guard refines the successful pattern with a condition on its binding. A missing value and a nonpositive value both return false. The macro also accepts alternative patterns, as the release's character-range example demonstrates. It retains pattern semantics while making the Boolean purpose explicit.

This does not make every pattern feature discussed by the language team stable. The March account separately describes nested alternatives and other pattern improvements behind nightly feature gates. Subslice patterns are the stabilized change here. Reading a forward-looking design discussion as a list of stable capabilities would produce examples that the release cannot accept.

I find that distinction important when introducing Rust syntax to another reader. A compact example is useful only when its compiler requirements are clear. The stable feature should be explained on its own terms before nearby experiments are treated as available tools.

Panic locations point to the failing call

The release also changes panic locations for the relevant Option and Result extraction methods. Previously an invalid unwrap could report a line inside core implementation code. Rust 1.42 reports the location where the application invoked the operation. The panic still describes the failed assumption, but the source location now identifies the call that made it.

The mechanism uses #[track_caller] internally. The announcement explicitly says the annotation is not yet available for general stable use. Standard-library behavior and public availability of the supporting annotation therefore have different stabilization status in this release.

That diagnostic improvement complements the pattern changes. A pattern can describe which input shapes a branch accepts, while a caller location identifies where code assumed a value existed. Neither changes the application's policy for an absent value. They make the structural decision and the failed assumption easier to locate in the source that owns them.

The release also deprecates Error::description. Its borrowed string return type cannot directly provide a newly formatted description. Error types should use their Display and Debug implementations for that purpose. Earlier work had already supplied a default implementation so implementers did not have to define the method themselves. The new deprecation makes the direction explicit while retaining the old method under the stability policy. That is a different kind of compatibility change from adding a pattern form. Existing code keeps its callable method, but documentation and diagnostics direct new implementations toward the traits that can express the required formatting.