Summary
Allow irrefutable named struct, tuple struct and single-variant enum (but not bare tuple) patterns in argument position to elide type specifiers when those types are not generic.
Motivation
The current syntax is redundant - using a destructuring pattern in a function signature requires specifying the type twice, once within the pattern and again in "normal" position. For cases where this is allowed in the first place, it isn't providing any new information, as there is only one possible choice of input type for a given pattern, which is even named within the pattern itself. This is particularly noticeable for newtypes, where we may have a type exist to hang traits from or to enforce that data is provided in the right "units," while the actual logic of our function immediately unwraps its nominal input because it is, e.g. a message handler unwrapping a payload.
Explanation
It is already possible to use a pattern binding to destructure the arguments being passed into a function, like this:
fn my_func(Struct{foo, bar} : Struct, TupleStruct(baz, qux): TupleStruct) {
/* Can now use foo, bar, baz, quz as locals */
}
With implied type specifiers, the following is also valid for irrefutable patterns on non-generic named structs and tuple structs, where it means the same thing as above:
fn my_func(Struct{foo, bar}, TupleStruct(baz, qux)) {
/* Ditto */
}
This is intended as purely syntax sugar. The requirements dictating when destructuring is allowed do not change at all. Similarly, the details of pattern match syntax itself, and the semantics and lifetimes of the destructured fields, remains the same.
Because the language specifically avoids features that implicitly infer in type signatures, this would specifically exclude allowing the same syntax with bare tuples.
fn my_other_func((foo,bar)) {
/* Not allowed */
}
Drawbacks
It complicates the grammar and introduces a new special case. There doesn't seem to be any other apparent problem - it doesn't appear to preclude anything or complicate the language significantly.
Rationale and alternatives
- Don't do this
Prior art
The only language I know of that has pattern matching directly on function arguments like this is Haskell. However, while Haskell makes argument deconstruction a fundamental part of the language, the story there is very different because it's primarily applied to refutable patterns on generic arguments. The langauge also makes the top-level type signature entirely optional, so even in the analogous case, Haskell users don't have the same overhead. This RFC (and AFAIK any likely RFC in the future) is not nearly as powerful as Haskell's capabilities, so it's hard to make a meaingful comparison.
Unresolved questions
-
Normally in argument position,
&Pattern { field }
is useless because it can only be meaningfully applied when typed as&Pattern
, which in turn results in trying to movefield
through a reference. This change as currently written does not change that meaning, so implied type specifiers could only be used for consuming values, not taking by reference. Is it worth special-casing&Pattern { field }
, with no type specifier, to instead desugar intoPattern {field} : &Pattern
? (And similarly for&mut Pattern { field }
, etc) -
Is there a natural meaning for an elided generic type?
Struct<T> { thing }
is currently a grammar error, so the generic types can't be explicitly specified without changing that. Having the fields be generic over the appropriate bounds (alaimpl Trait
) is potentially very awkward to use, while inferring their types from use in the body is essentially global inference by the backdoor.
Future possibilities
- Allowing refutable patterns in an argument context is an obvious extension of this idea, but is left to a future RFC as it 1) is technically orthogonal to this one, since pattern matching already exists, 2) impacts much more on the language's functionality than the syntax, and 3) is much less obvious in its value and implications.