Skip to content
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
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions active/0000-matches-macro.md
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?
Copy link
Contributor

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 the if let proposal.

The most reasonable version of it IMHO is that is is a keyword, and is an infix binary operator, where EXPRESSION is PATTERN is an expression of type bool and does basically the same thing as matches!(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.)

Copy link
Contributor

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!() over is is that matches!() can very easily allow for alternative patterns, as is being suggested here. However is cannot support alternative patterns without either being confusing (because | is a valid operator that one would expect to operate on the bool result of the is operator) or requiring additional syntax to enclose the set of patterns.

While I appreciate the syntactic simplicity of is, this suggests that matches!() may be the better approach (what with being implementable as a macro_rules! macro and not requiring the language grammar be extended in any way).


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
}
);
```