RFA-457 · Case file with fixtures · Case 429 of 694 · Compiler evidence
An Array Pattern Cannot Require More Elements Than Exist
A rest pattern can absorb extra elements but cannot create missing ones. Reduce the required prefix, change the fixed array type, or use a refutable slice match when runtime input may be shorter.
- Reviewed
- Rust
- Rust 1.98.1, edition 2024
- Targets
- all Rust targets
- Profiles
- dev, release, test
Direct answer
What this Rust failure means
- Why it happens
- A rest pattern can match zero or more remaining elements but cannot manufacture any explicit prefix or suffix positions absent from the fixed array.
- First discriminating check
- Count all explicit positions around .. and decide whether the input type or the algorithm's minimum-length requirement is wrong.
I wrote [first, second, third, ..] against a two-element array and expected the rest marker to make the pattern flexible. Rust emitted E0528 because the explicit part still requires at least three elements.
The failing fixture demonstrates the direction of ..: it can absorb an unknown remainder, but it cannot supply positions absent from the value.
Count the explicit positions
In [a, b, rest @ .., z], the pattern requires at least three elements: two before the rest and one after it. The rest may match zero elements, but the named or wildcard positions still need values.
The E0528 explanation describes this as a pattern requiring more elements than the array contains.
For [T; 2], the compiler can prove at compile time that three required positions are impossible.
Repair the contract, not only the count
The repaired fixture uses the exact two-element shape:
let [first, second] = values;
That is correct because both positions are meaningful. If the algorithm truly needs a third element, the repair belongs where the array is constructed or in the function signature, not in a pattern that pretends two are enough.
I ask whether the input type is wrong or the destructuring assumption is wrong before deleting a binding.
Runtime slices need a non-match path
A slice can have different lengths at runtime. The pattern [first, second, third, ..] is valid against &[T], but it is refutable. Short inputs go to another match arm or make an if let condition false.
This is useful for parsers:
match bytes {
[tag, len, payload @ ..] => decode(*tag, *len, payload),
_ => Err(Truncated),
}
The slice documentation provides alternatives such as split_first when stepwise parsing reads better.
Rest may match zero elements
Developers sometimes read .. as “one or more.” It means any number, including zero. Therefore [first, rest @ ..] requires one element, while [rest @ ..] can match an empty slice.
I write boundary tests for exactly the minimum length and one less than the minimum. Those cases expose mistaken assumptions quickly.
Prefix and suffix requirements add together
For [header, body @ .., checksum], both edge elements are required even if body is empty. This makes compact packet decomposition possible, but the minimum length is two.
When a format has optional trailers, one pattern may become misleading. Separate arms for the long and short forms can state the alternatives better.
I avoid indexing into the remainder unless another check proves its size.
Const-generic APIs should state minimums differently
Stable Rust cannot generally express every arithmetic constraint such as N >= 3 directly in an ordinary const-generic signature. If the function accepts all [T; N], a fixed three-position destructure cannot be valid for every instantiation.
I may accept a concrete [T; 3], accept a slice and return an error for short input, or use methods that expose optional positions. The interface should match who is responsible for length validation.
Avoid converting only to satisfy a pattern
Collecting an iterator into a vector just to use slice syntax may allocate and lose streaming behaviour. If I need three successive items, explicit next() calls with structured errors can be more faithful.
Patterns are excellent when the data already has sequence shape. They should not dictate an unnecessary representation change.
My E0528 checklist
For network and file formats, I attach the minimum-length statement to the operation name and error: “header requires three bytes” is more useful than “pattern did not match.” The pattern remains the executable check, while the returned error carries domain context. This turns a compact destructure into a maintainable boundary. It also lets fuzz tests assert that every shorter input returns a controlled truncation error rather than panicking through indexing.
- How many explicit positions does the pattern require?
- Can the rest portion legally be empty?
- What fixed length does the array type guarantee?
- Does the algorithm need more data than the producer supplies?
- Would a slice match make short input an explicit runtime case?
- Are prefix and suffix requirements both counted?
- Would iterator methods preserve the source's cost model better?
The core principle is that flexibility only applies to the remainder. Every explicit array or slice position is a real minimum-length requirement. I align that requirement with the producer's type and make short input visible wherever it can genuinely occur.