Skip to content

Commit

Permalink
Add core::macros::matches!( $expr, $pat ) -> bool
Browse files Browse the repository at this point in the history
# Motivation

This macro is:

* General-purpose (not domain-specific)
* Simple (the implementation is short)
* Very popular [on crates.io](https://crates.io/crates/matches)
  (currently 37th in all-time downloads)
* The two previous points combined make it number one in
  [left-pad index](https://twitter.com/bascule/status/1184523027888988160)
  score

As such, I feel it is a good candidate for inclusion in the standard library.

In fact I already felt that way five years ago:
rust-lang#14685
(Although the proof of popularity was not as strong at the time.)

Back then, the main concern was that this macro may not be quite
universally-enough useful to belong in the prelude.

# API

Therefore, this PR adds the macro such that using it requires one of:

```
use core::macros::matches;
use std::macros::matches;
```

Like arms of a `match` expression,
the macro supports multiple patterns separated by `|`
and optionally followed by `if` and a guard expression:

```
let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));

let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));
```

# Implementation constraints

A combination of reasons make it tricky
for a standard library macro not to be in the prelude.

Currently, all public `macro_rules` macros in the standard library macros
end up “in the prelude” of every crate not through `use std::prelude::v1::*;`
like for other kinds of items,
but through `#[macro_use]` on `extern crate std;`.
(Both are injected by `src/libsyntax_ext/standard_library_imports.rs`.)

`#[macro_use]` seems to import every macro that is available
at the top-level of a crate, even if through a `pub use` re-export.

Therefore, for `matches!` not to be in the prelude, we need it to be
inside of a module rather than at the root of `core` or `std`.

However, the only way to make a `macro_rules` macro public
outside of the crate where it is defined
appears to be `#[macro_export]`.
This exports the macro at the root of the crate
regardless of which module defines it.
See [macro scoping](
https://doc.rust-lang.org/reference/macros-by-example.html#scoping-exporting-and-importing)
in the reference.

Therefore, the macro needs to be defined in a crate
that is not `core` or `std`.

# Implementation

This PR adds a new `matches_macro` crate as a private implementation detail
of the standard library.
This crate is `#![no_core]` so that libcore can depend on it.
It contains a `macro_rules` definition with `#[macro_export]`.

libcore and libstd each have a new public `macros` module
that contains a `pub use` re-export of the macro.
Both the module and the macro are unstable, for now.

The existing private `macros` modules are renamed `prelude_macros`,
though their respective source remains in `macros.rs` files.
  • Loading branch information
SimonSapin committed Oct 23, 2019
1 parent f466f52 commit f69293a
Show file tree
Hide file tree
Showing 11 changed files with 99 additions and 4 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,7 @@ checksum = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
name = "core"
version = "0.0.0"
dependencies = [
"matches_macro",
"rand 0.7.0",
]

Expand Down Expand Up @@ -1900,6 +1901,10 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"

[[package]]
name = "matches_macro"
version = "0.0.0"

[[package]]
name = "mdbook"
version = "0.3.1"
Expand Down
3 changes: 3 additions & 0 deletions src/libcore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ path = "../libcore/tests/lib.rs"
name = "corebenches"
path = "../libcore/benches/lib.rs"

[dependencies]
matches_macro = { path = "../libmatches_macro" }

[dev-dependencies]
rand = "0.7"

Expand Down
12 changes: 11 additions & 1 deletion src/libcore/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
#![feature(iter_once_with)]
#![feature(lang_items)]
#![feature(link_llvm_intrinsics)]
#![feature(matches_macro)]
#![feature(never_type)]
#![feature(nll)]
#![feature(exhaustive_patterns)]
Expand Down Expand Up @@ -134,7 +135,16 @@
use prelude::v1::*;

#[macro_use]
mod macros;
#[path = "macros.rs"]
mod prelude_macros;

/// Macros that are not in the prelude and need to be imported explicitly
#[unstable(feature = "matches_macro", issue = "0")]
pub mod macros {
#[unstable(feature = "matches_macro", issue = "0")]
#[doc(inline)]
pub use matches_macro::matches;
}

#[macro_use]
mod internal_macros;
Expand Down
2 changes: 1 addition & 1 deletion src/libcore/prelude/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pub use crate::{
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
#[allow(deprecated)]
#[doc(no_inline)]
pub use crate::macros::builtin::{
pub use crate::prelude_macros::builtin::{
RustcDecodable,
RustcEncodable,
bench,
Expand Down
10 changes: 10 additions & 0 deletions src/libmatches_macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
authors = ["The Rust Project Developers"]
name = "matches_macro"
version = "0.0.0"
autotests = false
autobenches = false
edition = "2018"

[lib]
path = "lib.rs"
29 changes: 29 additions & 0 deletions src/libmatches_macro/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#![no_core]
#![feature(no_core)]
#![feature(staged_api)]
#![doc(test(no_crate_inject))]

/// Returns whether the given expression matches (any of) the given pattern(s).
///
/// # Examples
///
/// ```
/// #![feature(matches_macro)]
/// use std::macros::matches;
///
/// let foo = 'f';
/// assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
///
/// let bar = Some(4);
/// assert!(matches!(bar, Some(x) if x > 2));
/// ```
#[macro_export]
#[unstable(feature = "matches_macro", issue = "0")]
macro_rules! matches {
($expression:expr, $( $pattern:pat )|+ $( if $guard: expr )?) => {
match $expression {
$( $pattern )|+ $( if $guard )? => true,
_ => false
}
}
}
12 changes: 11 additions & 1 deletion src/libstd/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@
#![feature(linkage)]
#![feature(log_syntax)]
#![feature(manually_drop_take)]
#![feature(matches_macro)]
#![feature(maybe_uninit_ref)]
#![feature(maybe_uninit_slice)]
#![feature(needs_panic_runtime)]
Expand Down Expand Up @@ -353,7 +354,16 @@ extern crate cfg_if;

// The standard macros that are not built-in to the compiler.
#[macro_use]
mod macros;
#[path = "macros.rs"]
mod prelude_macros;

/// Macros that are not in the prelude and need to be imported explicitly
#[unstable(feature = "matches_macro", issue = "0")]
pub mod macros {
#[unstable(feature = "matches_macro", issue = "0")]
#[doc(inline)]
pub use core::macros::matches;
}

// The Rust prelude
pub mod prelude;
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/macros/unknown-builtin.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ LL | macro_rules! unknown { () => () }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: cannot find a built-in macro with name `line`
--> <::core::macros::builtin::line macros>:1:1
--> <::core::prelude_macros::builtin::line macros>:1:1
|
LL | () => { }
| ^^^^^^^^^
Expand Down
13 changes: 13 additions & 0 deletions src/test/ui/matches_macro_imported.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// run-pass

#![feature(matches_macro)]

use std::macros::matches;

fn main() {
let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));

let foo = '_';
assert!(!matches!(foo, 'A'..='Z' | 'a'..='z'));
}
7 changes: 7 additions & 0 deletions src/test/ui/matches_macro_not_in_the_prelude.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![feature(matches_macro)]

fn main() {
let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
//~^ Error: cannot find macro `matches` in this scope
}
8 changes: 8 additions & 0 deletions src/test/ui/matches_macro_not_in_the_prelude.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
error: cannot find macro `matches` in this scope
--> $DIR/matches_macro_not_in_the_prelude.rs:5:13
|
LL | assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
| ^^^^^^^

error: aborting due to previous error

0 comments on commit f69293a

Please sign in to comment.