This post is more a problem statement than a proposed solution, but I'm curious what people think.
std::matches is a very useful macro. However, it can be a bit awkward when you want to branch on something NOT matching:
if !matches!(val, pattern) {
...
}
One alternative is to use a match expression, but that's exactly what matches! is designed to simplify.
Another is to use a let binding:
let is_match = matches!(val, pattern);
if !is_match {
...
}
...but that's a lengthy workaround for the awkwardness of the !matches! syntax.
Below are some spitball proposals for solving this problem.
Macro providing the opposite of matches!
Unfortunately there's not an immediately obvious name to me so it seems like mega bikeshed territory.
Some unenthusiastic proposals:
not_matches!
mismatches!
Pie-in-the-sky new first-class branching keyword: unless
An unless keyword sure would be handy here, but I know a lot of people dislike the idea because it's easily abused to write unclear backwards-logic unless used in very specific circumstances where it's clearer, such as this one.
Still, I think it'd be my preferred solution to this problem, and would generally be nice whenever you want the opposite of a macro that returns a bool:
use std::ops::Not;
if matches!(val, pattern).not() {
...
}
Perl and Ruby have this and I do agree it is handy in the right circumstances. But I also feel unless blocks become a liability due to confusion once combined with else blocks/chains. It could be restricted to the non-chained case, thought that would likely make it harder to justify a new keyword.
I agree that !matches! looks silly, but it feels unlikely to me that something else would overcome the "additional thing to learn" hurdle. We don't even have .is_not_empty() on slices, for example.
Agreed, but I think there's a way to keep that but give it value to the reader: force the body to be diverging. That way as soon as you see the unless keyword you know it's a precondition check, simple early return, etc. And in those the negation of the logic is actually helpful, since the condition is what's true after the statement, just like with assert!(x > 0);.
Yes, please, .not() on bool suits the language patterns in a very ergonomic manner, so feel free to add some support on that prelude suggestion: while matches! is currently the main pervasive macro used in condition position, there may be more in the future, and we should have a generalized way to avoid writing !something!(…) code, or having to define the negation of each and every method / macro in existence (not_something!, etc.)
While prefix ! has the "advantage" of being less surprising for people used to other languages1, and while it can be made less unreadable by using a space after it: if ! matches!(…), we will always have the problem of a condition using a method chain:
if !base.method1(…)
.method2(…)
.method3(…)
.contains(…)
{
…
}
vs.
if base.method1(…)
.method2(…)
.method3(…)
.contains(…)
.not()
{
…
}
1I am personally not a fan of that argument, since it leads to repeating errors from the past, but I recognize that showcasing too many syntax differences within a language must be avoided, at least when that language starts. But not only is Rust now a well-known language, it would also be unfair to qualify .not() as a bizarre syntax.
It does get pretty funky really soon! But I think it isn't too bad. Might be easy to miss accidentally during code review, but otherwise the syntax is using what everybody knows. The not() option was also suggested earlier, but I'm not a fan of it because the negation is not where I expect it to be.
That's a good alternative! A bit more verbose for sure, but definitely won't be overlooked on code review. However I'm going to stick with my funny looking approach for the time being ^^
assert_matches!(..) isn't useful when you want assert!( ! matches!( .. ) ). The correct single macro would be assert_not_matches! or maybe assert_not!(matches!(_)).
That it's possible to make the mistake of assuming you could go from assert!(!matches!( to assert_matches!( just goes to show how easy it is to miss that leading ! among the rest of the punctuation noise.
For the assert case specifically, I think it would make sense to have assert_not!(; it could provide a better error message than assert!(!.
I prefer to avoid them when possible, and if I can write assert!(! to avoid it I will. I'm sure the crate is very useful if you make extensive use of that or other of its macros though.
Wow the linked RFC is pretty cool and I had no idea it existed, I would love to see that. It seems there's no prior art section in the RFC, but for example, pytest does this too:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 1 item
test_sample.py F [100%]
================================= FAILURES =================================
_______________________________ test_answer ________________________________
def test_answer():
> assert inc(3) == 5
E assert 4 == 5
E + where 4 = inc(3)
test_sample.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 4 == 5
============================ 1 failed in 0.12s =============================
This will never land in Rust, in any shape or form. That's because it's really a cruft for idealists, not a solution to a real problem, and I am talking about both not_matches and unless. Both are entirely superfluous and don't really improve source code readability, as with them you now have two ways of negation - ! and not_/unless. Negation is too fundamental and simple a concept to have multiple ways of doing it. When considering a language change, make sure you test it in some way before suggesting it. For example, go on, write a not_matches macro, replace all of your !matches with not_matches, compare the sources and ask the following questions:
Does it really improve readability that much?
Was it that hard to write the macro by yourself?
Can you improve the situation in some other way? Like using an IDE with proper highlighting of macro calls that is visually distinct from operators.
I think @burjui has it right in terms of what the pragmatic (non-) solution is to this. !matches!(x, y) is not great as an expression, but it's a general problem with all macros that expand to boolean expressions - including cfg!() etc, and most solutions don't seem to be proportional.
I still couldn't resist putting in my thoughts though, but it's not with the goal of changing Rust, just musing. The following could be an idea:
let x = Some(1);
if matches!(x, not Some(1)) {
}
The negation is moved into the pattern. matches!(x, not Some(1)) is equivalent to !matches!(x, Some(1)). This is readable but it has the drawback that a keyword like not has no precedent. And to be consistent, the same keyword should be usable in if let and matches as well - and have we ever seen utility for it there?
Matches example below. This doesn't feel easy to reason about at all, not a great feature
match (1, Some(2)) {
not (1, _) => {}
(1, None) => {}
(1, Some(_)) => {}
// The three cases should cover it
}
fn main(){
macro_rules! not_some {
($($ty:ty)?) => { None $(::<$ty>)? }
}
if let not_some!() = None::<u32> {
println!("Not Some");
}
if let not_some!(u32) = None {
println!("Not Some");
}
macro_rules! tuple {
($($elem:pat),* $(,)?)=>{ ($($elem,)*) }
}
let tuple!(a, b) = (0, 1);
println!("{} {}", a, b);
}
This obviously has all the downsides of macros, like having to write full paths inside the macro for #[macro_export]-ed macros, and macros being exported at the root of the crate.
It didn't come to (my) mind before, but now that @bluss has mentioned them, negated patterns seem an "obviously" missing thing, the already mentioned caveats aside.
This is actually just a None pattern, not a not Some(value) pattern, which AFAICT cannot be easily expressed in a macro. E.g., a not Some(5) pattern would succeed on a Some(3) value as well.
Other than solving OP's problem statement, as already mentioned, they might be useful to filter out result patterns:
if matches!(val, not pattern) { ... } // addressing OP's problem
if let res @ not Err(Fatal) = fn_returning_result() { /* res is type Result */ }
if let Err(err @ not Fatal) = fn_returning_result() { /* err is error type */ }
Exact syntax of course TBD, as well as how this would interact with match if guards and | arms.