Skip to content

Commit f69293a

Browse files
committed
Add core::macros::matches!( $expr, $pat ) -> bool
# 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: #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.
1 parent f466f52 commit f69293a

11 files changed

+99
-4
lines changed

Cargo.lock

+5
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,7 @@ checksum = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
585585
name = "core"
586586
version = "0.0.0"
587587
dependencies = [
588+
"matches_macro",
588589
"rand 0.7.0",
589590
]
590591

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

1904+
[[package]]
1905+
name = "matches_macro"
1906+
version = "0.0.0"
1907+
19031908
[[package]]
19041909
name = "mdbook"
19051910
version = "0.3.1"

src/libcore/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ path = "../libcore/tests/lib.rs"
2020
name = "corebenches"
2121
path = "../libcore/benches/lib.rs"
2222

23+
[dependencies]
24+
matches_macro = { path = "../libmatches_macro" }
25+
2326
[dev-dependencies]
2427
rand = "0.7"
2528

src/libcore/lib.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
#![feature(iter_once_with)]
8686
#![feature(lang_items)]
8787
#![feature(link_llvm_intrinsics)]
88+
#![feature(matches_macro)]
8889
#![feature(never_type)]
8990
#![feature(nll)]
9091
#![feature(exhaustive_patterns)]
@@ -134,7 +135,16 @@
134135
use prelude::v1::*;
135136

136137
#[macro_use]
137-
mod macros;
138+
#[path = "macros.rs"]
139+
mod prelude_macros;
140+
141+
/// Macros that are not in the prelude and need to be imported explicitly
142+
#[unstable(feature = "matches_macro", issue = "0")]
143+
pub mod macros {
144+
#[unstable(feature = "matches_macro", issue = "0")]
145+
#[doc(inline)]
146+
pub use matches_macro::matches;
147+
}
138148

139149
#[macro_use]
140150
mod internal_macros;

src/libcore/prelude/v1.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub use crate::{
8282
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
8383
#[allow(deprecated)]
8484
#[doc(no_inline)]
85-
pub use crate::macros::builtin::{
85+
pub use crate::prelude_macros::builtin::{
8686
RustcDecodable,
8787
RustcEncodable,
8888
bench,

src/libmatches_macro/Cargo.toml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
authors = ["The Rust Project Developers"]
3+
name = "matches_macro"
4+
version = "0.0.0"
5+
autotests = false
6+
autobenches = false
7+
edition = "2018"
8+
9+
[lib]
10+
path = "lib.rs"

src/libmatches_macro/lib.rs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#![no_core]
2+
#![feature(no_core)]
3+
#![feature(staged_api)]
4+
#![doc(test(no_crate_inject))]
5+
6+
/// Returns whether the given expression matches (any of) the given pattern(s).
7+
///
8+
/// # Examples
9+
///
10+
/// ```
11+
/// #![feature(matches_macro)]
12+
/// use std::macros::matches;
13+
///
14+
/// let foo = 'f';
15+
/// assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
16+
///
17+
/// let bar = Some(4);
18+
/// assert!(matches!(bar, Some(x) if x > 2));
19+
/// ```
20+
#[macro_export]
21+
#[unstable(feature = "matches_macro", issue = "0")]
22+
macro_rules! matches {
23+
($expression:expr, $( $pattern:pat )|+ $( if $guard: expr )?) => {
24+
match $expression {
25+
$( $pattern )|+ $( if $guard )? => true,
26+
_ => false
27+
}
28+
}
29+
}

src/libstd/lib.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@
276276
#![feature(linkage)]
277277
#![feature(log_syntax)]
278278
#![feature(manually_drop_take)]
279+
#![feature(matches_macro)]
279280
#![feature(maybe_uninit_ref)]
280281
#![feature(maybe_uninit_slice)]
281282
#![feature(needs_panic_runtime)]
@@ -353,7 +354,16 @@ extern crate cfg_if;
353354

354355
// The standard macros that are not built-in to the compiler.
355356
#[macro_use]
356-
mod macros;
357+
#[path = "macros.rs"]
358+
mod prelude_macros;
359+
360+
/// Macros that are not in the prelude and need to be imported explicitly
361+
#[unstable(feature = "matches_macro", issue = "0")]
362+
pub mod macros {
363+
#[unstable(feature = "matches_macro", issue = "0")]
364+
#[doc(inline)]
365+
pub use core::macros::matches;
366+
}
357367

358368
// The Rust prelude
359369
pub mod prelude;

src/test/ui/macros/unknown-builtin.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ LL | macro_rules! unknown { () => () }
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66

77
error: cannot find a built-in macro with name `line`
8-
--> <::core::macros::builtin::line macros>:1:1
8+
--> <::core::prelude_macros::builtin::line macros>:1:1
99
|
1010
LL | () => { }
1111
| ^^^^^^^^^

src/test/ui/matches_macro_imported.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// run-pass
2+
3+
#![feature(matches_macro)]
4+
5+
use std::macros::matches;
6+
7+
fn main() {
8+
let foo = 'f';
9+
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
10+
11+
let foo = '_';
12+
assert!(!matches!(foo, 'A'..='Z' | 'a'..='z'));
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#![feature(matches_macro)]
2+
3+
fn main() {
4+
let foo = 'f';
5+
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
6+
//~^ Error: cannot find macro `matches` in this scope
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: cannot find macro `matches` in this scope
2+
--> $DIR/matches_macro_not_in_the_prelude.rs:5:13
3+
|
4+
LL | assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
5+
| ^^^^^^^
6+
7+
error: aborting due to previous error
8+

0 commit comments

Comments
 (0)