Summary
if-let-guard in match should be expanded to allow different if-let-guards in or patterns.
// Existing if-let-guard
match foo {
_ if let x = bar => {}
}
// Proposed if-let-guard-in-or-patterns
match foo {
(A if let x = bar) | (B if let x = baz) => {}
}
Note the use of (...)
for compatibility with existing programs/patterns.
Motivation
Given:
/// A pattern element.
pub(crate) enum PatternElement<T: PatternTypes> {
Arrow,
Identifier(usize),
StringKey(usize),
RegexKey(usize),
ParameterKey(usize),
KeySubtree(usize),
ValueSubtree(usize),
ApplyPredicate(usize, PhantomData<fn(&PatternConstants<T>) -> &Predicate<T>>),
SkipStringKey(usize),
SkipRegexKey(usize),
SkipParameterKey(usize),
SkipKeySubtree(usize),
SkipValueSubtree(usize),
SkipApplyPredicate(usize, PhantomData<fn(&PatternConstants<T>) -> &Predicate<T>>),
End
}
// These use more memory than the above. They're easier to work with tho.
// TODO replace with (StringKey(x) if let skip = false) | (SkipStringKey(x) if let skip = true)
// (if those ever become a thing)
enum PatternElementHelper {
Arrow,
Identifier(usize),
StringKey(usize, bool),
RegexKey(usize, bool),
ParameterKey(usize, bool),
KeySubtree(usize, bool),
ValueSubtree(usize, bool),
ApplyPredicate(usize, bool),
End
}
impl<T: PatternTypes> From<PatternElement<T>> for PatternElementHelper {
fn from(a: PatternElement<T>) -> PatternElementHelper {
match a {
PatternElement::Arrow => PatternElementHelper::Arrow,
PatternElement::Identifier(x) => PatternElementHelper::Identifier(x),
PatternElement::StringKey(x) => PatternElementHelper::StringKey(x, false),
PatternElement::SkipStringKey(x) => PatternElementHelper::StringKey(x, true),
PatternElement::RegexKey(x) => PatternElementHelper::RegexKey(x, false),
PatternElement::SkipRegexKey(x) => PatternElementHelper::RegexKey(x, true),
PatternElement::ParameterKey(x) => PatternElementHelper::ParameterKey(x, false),
PatternElement::SkipParameterKey(x) => PatternElementHelper::ParameterKey(x, true),
PatternElement::KeySubtree(x) => PatternElementHelper::KeySubtree(x, false),
PatternElement::SkipKeySubtree(x) => PatternElementHelper::KeySubtree(x, true),
PatternElement::ValueSubtree(x) => PatternElementHelper::ValueSubtree(x, false),
PatternElement::SkipValueSubtree(x) => PatternElementHelper::ValueSubtree(x, true),
PatternElement::ApplyPredicate(x, _) => PatternElementHelper::ApplyPredicate(x, false),
PatternElement::SkipApplyPredicate(x, _) => PatternElementHelper::ApplyPredicate(x, true),
PatternElement::End => PatternElementHelper::End,
}
}
}
Allowing the syntax in that TODO comment would allow getting rid of PatternElementHelper
entirely, with no additional runtime cost (either processing or memory). This feature would be an adaptation of explicit fallthrough for use with pattern matching.
Instead, replacing PatternElement
with PatternElementHelper
would cause a 33% increase in memory usage for the structs that contain PatternElement
s.
Guide-level explanation
[TODO]
Reference-level explanation
[TODO]
Drawbacks
This proposal introduces new syntax. It also requires look-ahead, as the proposed syntax is somewhat ambiguous with tuples.
Rationale and alternatives
This is seems to be the simplest adaptation of fallthrough for use with pattern matching, with a good cost/benefit tradeoff. It expands on existing syntax instead of introducing completely novel syntax, avoids the pitfalls of implicit fallthrough, avoids introducing non-lexical flow control (such as goto, etc), and correctly fulfills the need it is meant to fulfill.
A possible alternative could have been unreferenceablefolded fields. However, being able to fold bools into the enum variant would be problematic for the same reason we don't support packed structs anymore. More importantly, there is no good way to support mutating folded fields through &mut Enum
. For example,
enum Foo {
A(fold bool, Mutex<()>),
B(fold bool, Mutex<()>),
}
fn bar(&mut Foo) {
// how would you change A(false, mutex) into A(true, mutex), without unsafe code?
}
let mut x = Foo::A(false, Mutex::new(())); // so far so good
bar(&mut x);
Prior art
- C and C++ have implicit fallthrough. It's such an issue that gcc and clang have a flag
-Wimplicit-fallthrough
to warn on implicit fallthrough, such that the programmer is required to add a// fall through
comment where fallthrough is desired. - [TODO]
Unresolved questions
- Are there other alternatives to this?
- [TODO]
Future possibilities
Chaining if-let and/or allowing (...)
around arbitrary parts of patterns is out of scope for this proposal.