-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Add a matches!(expression, pattern) macro. #163
Closed
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
- Start Date: 2014-07-14 | ||
- RFC PR #: | ||
- Rust Issue #: | ||
|
||
# Summary | ||
|
||
Add a `matches!(expression, pattern)` macro that returns, as a boolean, whether the value of a given expression matches a given pattern. | ||
|
||
# Motivation | ||
|
||
When writing parsing code (for example [in rust-url](https://github.com/SimonSapin/rust-url/blob/de6bd47a1f1ffcc1b1388ecfd754f5615ff44fc4/src/parser.rs)), I often find myself writing complex conditionnals like this: | ||
|
||
```rust | ||
let input: &str = /* ... */; | ||
if input.len() >= 2 | ||
&& match input.char_at(0) { '+' | '-' => true, _ => false } | ||
&& match input.char_at(1) { '0'..'9' => true, _ => false } { | ||
// Parse signed number | ||
} else if /* ... */ | ||
``` | ||
|
||
The `=> true, _ => false` part here is obviously redundant and adds more noise than it helps the readability of this code. We can get rid of it with a simple macro: | ||
|
||
```rust | ||
let input: &str = /* ... */; | ||
if input.len() >= 2 | ||
&& matches!(input.char_at(0), '+' | '-') | ||
&& matches!(input.char_at(1), '0'..'9') { | ||
// Parse signed number | ||
} else if /* ... */ | ||
``` | ||
|
||
This macro feels general-purpose enough that it should be in the standard library. Copying it over and over in many projects is unfortunate, and the packaging/distribution overhead (even with Cargo) makes it ridiculous to have a dedicated library. | ||
|
||
**Note:** This is different from the [`if let`](https://github.com/rust-lang/rfcs/pull/160) proposal, which replaces an entire `if` expression and can bind new names, while `matches!()` can be used as part of larger boolean expression (as in the example above) and does not make available new names introduced in the pattern. I believe the two proposals are complementary rather than competing. | ||
|
||
|
||
# Detailed design | ||
|
||
Add the following to `src/libstd/macros.rs` | ||
|
||
```rust | ||
/// Return whether the given expression matches the given patterns. | ||
/// | ||
/// # Example | ||
/// | ||
/// ``` | ||
/// let input: &str = /* ... */; | ||
/// if input.len() >= 2 | ||
/// && matches!(input.char_at(0), '+' | '-') | ||
/// && matches!(input.char_at(1), '0'..'9') { | ||
/// // Parse signed number | ||
/// } else if /* ... */ | ||
/// ``` | ||
#[macro_export] | ||
macro_rules! matches( | ||
($expression: expr, $($pattern:pat)|*) => ( | ||
match $expression { | ||
$($pattern: pat)|+ => true, | ||
_ => false | ||
} | ||
); | ||
) | ||
``` | ||
|
||
# Drawbacks | ||
|
||
This adds a new name to the "prelude": it is available implicitly, without being imported by a `use` statement or similar. | ||
|
||
# Alternatives | ||
|
||
* Status quo: potential users have to use the more verbose `match` statement, or define their own copy of this macro. | ||
* Distribute this macro with rustc somehow, but not in the prelude. At the moment, users would have to write `#![feature(phase)] #[phase(plugin)] extern crate some_crate_name;` to import all macros defined in a given crate. Assuming we want to distribute more non-prelude macros with rustc in the future, this would mean either: | ||
* Adding a single "catch-all" crate called `std_macros` (or something). Since the `use` statement does not apply, users can not choose to only import some macros into their namespace, this crate is all-or-nothing. | ||
* Adding one more crate for every macro. This seems way overkill. | ||
* Adding a better mechanism for namespacing and importing macros, which would need to be designed and RFC’ed separately. | ||
|
||
|
||
# Unresolved questions | ||
|
||
Should this be a language feature rather than a macro? If so, what would the syntax look like? | ||
|
||
Should the macro be named `matches!`, `is_match!`, or something else? | ||
|
||
Should the macro also support guards? | ||
|
||
```rust | ||
($expression: expr, $($pattern:pat)|* if $guard: expr) => ( | ||
match $expression { | ||
$($pattern: pat)|+ if $guard => true, | ||
_ => false | ||
} | ||
); | ||
``` |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the
is
operator that was discussed at theif let
proposal.The most reasonable version of it IMHO is that
is
is a keyword, and is an infix binary operator, whereEXPRESSION is PATTERN
is an expression of typebool
and does basically the same thing asmatches!(EXPRESSION, PATTERN)
, and doesn't allow binding names.(This is intended as an answer to the second question, without implying any opinion about the first.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's worth noting that the benefit of
matches!()
overis
is thatmatches!()
can very easily allow for alternative patterns, as is being suggested here. Howeveris
cannot support alternative patterns without either being confusing (because|
is a valid operator that one would expect to operate on thebool
result of theis
operator) or requiring additional syntax to enclose the set of patterns.While I appreciate the syntactic simplicity of
is
, this suggests thatmatches!()
may be the better approach (what with being implementable as amacro_rules!
macro and not requiring the language grammar be extended in any way).