In my opinion, the fact that matches!() can be implemented purely as a macro without any problems indicates exactly that it should not be a language feature.
It's already syntactic sugar anyway, basically – it stands for a match with a catch-all that defaults to false. The advantages (if any) of having it as a builtin feature would be marginal, but it would be heavily redundant, because pattern matching already exists in the language.
Yeah, think this is not really an issue, but rather the artifact of current implementation which we’ll need to fix for libraryfication anyway.
The core problem is that the „text -> toke trees -> ast“ model is a lie.There‘s no such thing as universal token tree format, because proc macros and macro by example already are using TTs of different shapes. Now, the rest of the compiler „happens“ to use mbe-style token trees, but that’s a pretty ad hoc model.
The right way to think about TTs is as an interface between compiler and macros. When expanding a macro, compiler needs to lower its internal representation to the TT format, appropriate for the macro. The knowledge that $tt matches == but not ~= should be the part of this lowering layer.
To my mind, the infix matches is the obvious choice. If you know what a match statement is, the meaning of if foo matches Ok(0) { is immediately clear without learning any new rules.
I certainly wouldn't want a new sigil for this, especially not one with ~, which has many other meanings, like "approximately" and "not". The slightly shorter code can't possibly be worth the downside of adding one more thing for new users to stumble over.
I think that, if a language construct were to be added to replace the matches! macro, this would read quite nicely, even if the keyword matches is a bit long by Rust's standards (compare fn, impl, type, let, etc).
However what could make this worth it for me is the orthogonal feature of being able to pattern match while also using && to create larger boolean expressions in an if-style block. Currently this is possible in a match expr but it brings with it 2 levels of indentation for the match expression arms, which is not always desirable.
So basically I'd like something like this to be possible:
let opt = Some(42u8);
if let Some(num) = opt && num != 42 {
// take action
}
But that brings with it its own design issues w.r.t. composability of boolean expressions.
What about struct literals? matches! gets around any parse ambiguity because has surrounding delimiters.
What is the meaning of the following?
if foo { bar } matches baz { bar } { bar }
Think about what the parser needs to know for that to work, and what a human will have to do to understand it.
We already have a similar case when it comes to if and struct literals where we recover somewhat gracefully, but it is an ugly hack that I would like to avoid infecting other parts of the grammar.
#[derive(PartialEq, Eq)]
struct foo { bar: () }
type baz = foo;
const baz: foo = foo { bar: () };
const bar: () = ();
fn main() {
// if foo { bar } matches baz { bar } { bar }
if matches!(foo { bar }, baz) { bar } { bar }
if matches!(foo { bar }, baz { bar }) { bar }
}
The warnings (if not surpressed) point out the clarifications you can make to the code to remove the ambiguity and move it towards what the compiler is interpreting it as.
I believe the way the "brackets not allowed in expression of if" warning works is that if the name used is the name of a type but not of a value, the hint is emitted along with the name lookup error.
I feel like we should get if-let chaining (with bindings and &&) to work first. That gives enough expressive power to solve this problem, and makes it easy for a macro to reverse the syntax if someone wants the pattern on the right. If in practice people often want the pattern on the right, we could then consider adding something like is or matches as a reversed let.
if let chaining will mostly solve the problem, but:
I find the if let syntax rather ugly, and beginners are regularly confused by it
Writing the expression after the pattern often feels unintuitive, this is often compared to Yoda speech
An expression that evaluates to bool and can introduce bindings would be more powerful than if let, because it can be used outside of if expressions. For example, it can be used in an if guard, in Iterator::filter or str::contains:
let b = "hello world!".contains(|c| c matches 'a'..='z');
Of course, this is what matches!() exists for, but
An infix keyword would be more intuitive; to use matches!() you have to know if the expression comes first or the pattern.
An infix keyword is much more readable, especially if the expression is long. For example:
iter
.map(...)
.filter(...)
.next()
matches None
// vs.
matches!(
iter
.map(...)
.filter(...)
.next(),
None
)
It's better to have one syntax that works everywhere, rather than having to choose between if let and matches!().
A bool expression would also enable new code patterns, e.g.
x matches Some(y) || return 0;
println!("{}", y);
If x is Some(_), then the value is bound to y; otherwise, the function returns. Since return evaluates to !, which can be coerced to bool, the types check out.
The current alternatives to this pattern either require an additional level of indentation, or unwrap():
if let Some(y) = x {
println!("{}", y);
} else {
return 0;
}
// or
if let None = x {
return 0;
}
println!("{}", y.unwrap());
I have already said to you "I find binding after the =~ sign to be confusing and comparable to yoda speech. I would rather all bindings to be on the left side." here. Could we continue this discussion on that thread so the context is available to those trying to catch up?
Also dunno if you saw, could you please respond to my comment about bindings in match arms?
So, I do think that something like matches makes sense. I'm not suggesting we shouldn't do it. But we already haveif let, and extending that to allow chaining would be an intuitive extension for people who are already used to if let; I think we should do that regardless. Then, we could define matches as a trivial desugaring, with if let taking care of details like the scopes of bindings.
I wouldn't want to have matches without having bindings, because it's incredibly useful to write things like if expr matches Some(x) && another_expr(x). That has all the same issues as if let, in terms of the scopes of the bindings. I think we should have both, and I think we should define the semantics only once.