Match with a variable vs match with a placeholder? - rust

fn main() {
let f = 1;
match f {
foo => {
println!("{}", foo); // prints "1"
}
};
match f {
_ => {
println!("{}", f); // prints "1"
}
};
}
Is there any difference between the two matches? And could anyone point me where the first match is documented? The Rust book (until chapter 9) seems only mention the placeholder _, but not "you can put any name (e.g., foo in this case) as the match condition".

These are just patterns. Patterns are documented in more details further in the book. There are many forms of patterns:
Constants: 1, FOO
enum destructuring: Some(y), None
struct destructuring: Point { x, y }
Tuple destructuring: (42, foo)
Bindings: foo, foo # (42, bar)
Ranges: 1 ... 42
Placeholder: _
etc.
There is no difference here between your two examples, however, named patterns are not often seen as a top-level pattern in a match expression, but rather in sub-patterns, such as Some(foo). Named patterns are however extremely common for function parameters and let bindings.

There is a subtle difference:
#[derive(Debug)]
struct NoisyDrop(&'static str);
impl Drop for NoisyDrop {
fn drop(&mut self) {
println!("{self:?} dropped");
}
}
fn main() {
{
let v = NoisyDrop("With placeholder");
match v {
_ => {
println!("Inside match arm");
}
}
println!("After match arm");
}
println!();
{
let v = NoisyDrop("With name");
match v {
_foo => {
println!("Inside match arm");
}
}
println!("After match arm");
}
}
Playground.
This prints:
Inside match arm
After match arm
NoisyDrop("With placeholder") dropped
Inside match arm
NoisyDrop("With name") dropped
After match arm
This is because when we use an identifier pattern (_foo) the matched variable is moved into the arm and dropped at the end of it. This also means that any attempt to use it after the match will result in an error. On the other hand, when we match with a wildcard (_) the variable is not moved. Thus, we can use it after the match, and it also means that it is dropped at the end of the scope and not of the match.

Related

How to check enum with values

How do I check if a vector of enum has a certain member with values?
#[derive(PartialEq,Clone,Copy)]
enum TestResult {
Pass,
Fail {point: u8},
}
impl TestResult {
fn is_fail(&self) -> bool {
match *self {
TestResult::Fail{point:_} => true,
_ => false,
}
}
}
fn main() {
let v1 = vec![TestResult::Pass,
TestResult::Pass,
TestResult::Fail{point:50}];
if v1.contains(&TestResult::Pass) {
println!("result contains Pass");
}
if v1.iter().any(|&r| r.is_fail()) {
println!("result contains Fail");
}
}
This is working but is there a way to do this with Vec::contains()?
I want to check if TestResult::Fail is in a vector in the same way as for TestResult::Pass (no pattern matching. easy..)
There isn't really an easy way to do this, however we can make a simple macro to perform some pattern matching in the if statement.
/// Performs pattern matching contains on an IntoIter type
macro_rules! matches_any {
($iterable:expr, $($tokens:tt)+) => {{
let iter = ::std::iter::IntoIterator::into_iter($iterable);
iter.any(|x| matches!(x, $($tokens)+))
}};
}
The trick here is that we can use the matches! macro instead of a full match statement if all we want to know is if something matches a given pattern.
// Without matches!
match self {
TestResult::Fail {..} => true,
_ => false,
}
// With matches!
matches!(self, TestResult::Fail {..})
So now we can use this macro instead:
let v1 = vec![TestResult::Pass,
TestResult::Pass,
TestResult::Fail { point: 50 }];
if matches_any!(&v1, TestResult::Pass) {
println!("result contains Pass");
}
if matches_any!(&v1, TestResult::Fail {..}) {
println!("result contains Fail");
}

Why can't Rust find method for enum generated using proc_macro_attribute?

I am trying to write procedural macros that will accept a Rust enum like
#[repr(u8)]
enum Ty {
A,
B
}
and generate a method for the enum that will let me convert an u8 into an allowed variant like this
fn from_byte(byte: u8) -> Ty {
match {
0 => Ty::A,
1 => Ty::B,
_ => unreachable!()
}
}
This is what I have implemented using proc_macro lib. (no external lib)
#![feature(proc_macro_diagnostic)]
#![feature(proc_macro_quote)]
extern crate proc_macro;
use proc_macro::{TokenStream, Diagnostic, Level, TokenTree, Ident, Group, Literal};
use proc_macro::quote;
fn report_error(tt: TokenTree, msg: &str) {
Diagnostic::spanned(tt.span(), Level::Error, msg).emit();
}
fn variants_from_group(group: Group) -> Vec<Ident> {
let mut iter = group.stream().into_iter();
let mut res = vec![];
while let Some(TokenTree::Ident(id)) = iter.next() {
match iter.next() {
Some(TokenTree::Punct(_)) | None => res.push(id),
Some(tt) => {
report_error(tt, "unexpected variant. Only unit variants accepted.");
return res
}
}
}
res
}
#[proc_macro_attribute]
pub fn procmac(args: TokenStream, input: TokenStream) -> TokenStream {
let _ = args;
let mut res = TokenStream::new();
res.extend(input.clone());
let mut iter = input.into_iter()
.skip_while(|tt| if let TokenTree::Punct(_) | TokenTree::Group(_) = tt {true} else {false})
.skip_while(|tt| tt.to_string() == "pub");
match iter.next() {
Some(tt # TokenTree::Ident(_)) if tt.to_string() == "enum" => (),
Some(tt) => {
report_error(tt, "unexpected token. this should be only used with enums");
return res
},
None => return res
}
match iter.next() {
Some(tt) => {
let variants = match iter.next() {
Some(TokenTree::Group(g)) => {
variants_from_group(g)
}
_ => return res
};
let mut match_arms = TokenStream::new();
for (i, v) in variants.into_iter().enumerate() {
let lhs = TokenTree::Literal(Literal::u8_suffixed(i as u8));
if i >= u8::MAX as usize {
report_error(lhs, "enum can have only u8::MAX variants");
return res
}
let rhs = TokenTree::Ident(v);
match_arms.extend(quote! {
$lhs => $tt::$rhs,
})
}
res.extend(quote!(impl $tt {
pub fn from_byte(byte: u8) -> $tt {
match byte {
$match_arms
_ => unreachable!()
}
}
}))
}
_ => ()
}
res
}
And this is how I am using it.
use helper_macros::procmac;
#[procmac]
#[derive(Debug)]
#[repr(u8)]
enum Ty {
A,
B
}
fn main() {
println!("TEST - {:?}", Ty::from_byte(0))
}
The problem is this causing an error from the compiler. The exact error being
error[E0599]: no variant or associated item named `from_byte` found for enum `Ty` in the current scope
--> main/src/main.rs:91:32
|
85 | enum Ty {
| ------- variant or associated item `from_byte` not found here
...
91 | println!("TEST - {:?}", Ty::from_byte(0))
| ^^^^^^^^^ variant or associated item not found in `Ty`
Running cargo expand though generate the proper code. And running that code directly works as expected. And so I am stumped. It could be I am missing something about how proc_macros should be used since this is the first time I am playing with them and I don't see anything that would cause this error. I am following the sorted portion of the proc_macro_workshop0. Only change is, I am using TokenStream directly instead of using syn and quote crates. Also, if I mistype the method name, the rust compiler does suggest that a method with similar name exists.
Here is a Playground repro: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=02c1ee77bcd80c68967834a53c011e41
So, indeed what you mention is true: the expanded code could be copy-pasted and it would work. When this happens (having behavior from macro expansion and "manual copy-pasted expansion" differ), there are two possibilities:
macro_rules! metavariables
When emitting code using macro_rules! special captures, some of these captures are wrapped with special invisible parenthesis that already tell the parser how the thing inside should be parsed, which make it illegal to use in other places (for instance, one may capture a $Trait:ty, and then doing impl $Trait for ... will fail (it will parse $Trait as a type, thus leading to it being interpreted as a trait object (old syntax)); see also https://github.com/danielhenrymantilla/rust-defile for other examples.
This is not your case, but it's good to keep in mind (e.g. my initial hunch was that when doing $tt::$rhs if $tt was a :path-like capture, then that could fail).
macro hygiene/transparency and Spans
Consider, for instance:
macro_rules! let_x_42 {() => (
let x = 42;
)}
let_x_42!();
let y = x;
This expands to code that, if copy-pasted, does not fail to compile.
Basically the name x that the macro uses is "tainted" to be different from any x used outside the macro body, precisely to avoid misinteractions when the macro needs to define helper stuff such as variables.
And it turns out that this is the same thing that has happened with your from_byte identifier: your code was emitting a from_byte with private hygiene / a def_site() span, which is something that normally never happens for method names when using classic macros, or classic proc-macros (i.e., when not using the unstable ::proc_macro::quote! macro). See this comment: https://github.com/rust-lang/rust/issues/54722#issuecomment-696510769
And so the from_byte identifier is being "tainted" in a way that allows Rust to make it invisible to code not belonging to that same macro expansion, such as the code in your fn main.
The solution, at this point, is easy: forge a from_bytes Identifier with an explicit non-def_site() Span (e.g., Span::call_site(), or even better: Span::mixed_site() to mimic the rules of macro_rules! macros) so as to prevent it from getting that default def_site() Span that ::proc_macro::quote! uses:
use ::proc_macro::Span;
// ...
let from_byte = TokenTree::from(Ident::new("from_byte", Span::mixed_site()));
res.extend(quote!(impl $tt {
// use an interpolated ident rather than a "hardcoded one"
// vvvvvvvvvv
pub fn $from_byte(byte: u8) -> $tt {
match byte {
$match_arms
_ => unreachable!()
}
}
}))
Playground

Does this error message mean I can use pattern matching in for loops?

I do not expect the following code to work, but as part of grammar exploration, I tried in playground:
fn main() {
struct EOF {};
let lines = vec![Ok("line 1"), Ok("line 2"), Err(EOF {})];
for Ok(line) in lines {
println!("{}", line);
}
}
The error message is
error[E0005]: refutable pattern in `for` loop binding: `Err(_)` not covered
--> src/main.rs:4:9
|
4 | for Ok(line) in lines {
| ^^^^^^^^ pattern `Err(_)` not covered
According to the message above it looks like I only need to add a match arm for the Err case. But what is the right grammar to do so?
You can use patterns as the binding in a for loop, but not refutable patterns. The difference between refutable and irrefutable patterns is described here, but the gist of it is, if a pattern could fail, you can't use it in a let statement, a for loop, the parameter of a function or closure, or other places where the syntax specifically requires an irrefutable pattern.
An example of an irrefutable pattern being used in a for loop might be something like this:
let mut numbers = HashMap::new();
numbers.insert("one", 1);
numbers.insert("two", 2);
numbers.insert("three", 3);
for (name, number) in &numbers {
println!("{}: {}", name, number);
}
(name, number) is an irrefutable pattern, because any place where it type checks, it will match. It type checks here because the items being iterated over (defined by the implementation of IntoIterator for &HashMap) are tuples. You could also write the above as
for tuple in &numbers {
let (name, number) = tuple;
println!("{}: {}", name, number);
}
because let is another place where only irrefutable patterns are allowed.
Yes, you can use patterns in many places, but not all of them allow you to conditionally branch when there are multiple possible patterns.
A for loop is one place where you cannot add conditions. That's what the error is telling you with "refutable pattern": there's a pattern that will not be handled. Instead, you mostly use the pattern to perform destructuring of the loop variable:
struct Thing {
foo: u8,
}
fn main() {
let things = vec![Thing { foo: 1 }, Thing { foo: 2 }, Thing { foo: 3 }];
for Thing { foo } in things {
println!("{}", foo);
}
}
Conditional:
match
if let
while let
Unconditional:
for
let
function parameters
But what is the right grammar to do so?
This gets the result you want:
fn main() {
struct EOF;
let lines = vec![Ok("line 1"), Ok("line 2"), Err(EOF)];
for line in lines.into_iter().flat_map(|e| e) {
println!("{}", line);
}
}
Note that you can use flat_map here because Result implements the into_iter method provided by the IntoIterator trait.
This is another option using if let:
fn main() {
struct EOF;
let lines = vec![Ok("line 1"), Ok("line 2"), Err(EOF)];
for result in lines {
if let Ok(line) = result {
println!("{}", line);
}
}
}
You may also want to stop iteration on an Err case:
fn main() {
struct EOF;
let lines = vec![Ok("line 1"), Ok("line 2"), Err(EOF), Ok("line 3") ];
let mut lines_iter = lines.into_iter();
while let Some(Ok(line)) = lines_iter.next() {
println!("{}", line);
}
}

How can I pattern match against an Option<String>?

I can straight-forwardly match a String in Rust:
let a = "hello".to_string();
match &a[..] {
"hello" => {
println!("Matches hello");
}
_ => panic!(),
}
If I have an option type, it fails:
match Some(a) {
Some("hello") => {
println!("Matches some hello");
}
_ => panic!(),
}
because the types don't match:
error[E0308]: mismatched types
--> src/main.rs:5:14
|
5 | Some("hello") => {
| ^^^^^^^ expected struct `std::string::String`, found reference
|
= note: expected type `std::string::String`
found type `&'static str`
I can't do the [..] trick because we have an Option. The best that
I have come up with so far is:
match Some(a) {
Some(b) => match (&b[..]) {
"hello" => {
println!("Matches some, some hello");
}
_ => panic!(),
},
None => panic!(),
}
which works but is terrible for its verbosity.
In this case, my code is just an example. I do not control the creation of either the String or the Some(String) — so I can't change this type in reality as I could do in my example.
Any other options?
It's a known limitation of Rust's patterns.
Method calls (including internal methods for operators like ==) automatically call .deref() as needed, so String gets automagically turned into &str for comparisons with literals.
On the other hand, the patterns are quite literal in their comparisons, and find that String and &str are different.
There are two solutions:
Change Option<String> to Option<&str> before matching on it: Some(a).as_deref(). The as_deref() is a combo of as_ref() that makes Option<&String> (preventing move), and deref()/as_str() then unambiguously references it as a &str.
Use match guard: match Some(a) { Some(ref s) if s == "hello" => … }. Some(ref s) matches any String, and captures it as s: &String, which you can then compare in the if guard which does the usual flexible coercions to make it work.
See also:
Converting from Option<String> to Option<&str>
As of Rust 1.40, you can now call as_deref on Option<String> to convert it to Option<&str> and then match on it:
match args.nth(1).as_deref() {
Some("help") => {}
Some(s) => {}
None => {}
}
I found this because it is one of the clippy lints.
Look at this.
You cannot match on std::String, as you've found, only on &str. Nested pattern matches work, so if you can match on &str, you can match on Option<&str>, but still not on Option<String>.
In the working example, you turned the std::String into a &str by doing &a[..]. If you want to match on a Option<String>, you have to do the same thing.
One way is to use nested matches:
match a {
Some(ref s) => match &s[..] {
"hello" => /* ... */,
_ => /* ... */,
},
_ => /* ... */,
}
But then you have to duplicate the "otherwise" code if it's the same, and it's generally not as nice.
Instead, you can turn the Option<String> into an Option<&str> and match on this, using the map function. However, map consumes the value it is called on, moving it into the mapping function. This is a problem because you want to reference the string, and you can't do that if you have moved it into the mapping function. You first need to turn the Option<String> into a Option<&String> and map on that.
Thus you end up with a.as_ref().map(|s| /* s is type &String */ &s[..]). You can then match on that.
match os.as_ref().map(|s| &s[..]) {
Some("hello") => println!("It's 'hello'"),
// Leave out this branch if you want to treat other strings and None the same.
Some(_) => println!("It's some other string"),
_ => println!("It's nothing"),
}
In some cases, you can use unwrap_or to replace Option::None with a predefined &str you don't want to handle in any special way.
I used this to handle user inputs:
let args: Vec<String> = env::args().collect();
match args.get(1).unwrap_or(&format!("_")).as_str() {
"new" => {
print!("new");
}
_ => {
print!("unknown string");
}
};
Or to match your code:
let option = Some("hello");
match option.unwrap_or(&format!("unhandled string").as_str()) {
"hello" => {
println!("hello");
}
_ => {
println!("unknown string");
}
};

Is there a way to use custom patterns such as a regex or functions in a match?

I'm writing a toy programming language in Rust. I prototyped the parser logic in Ruby:
def rd_tree(chars)
loop do
case c = chars.next
when /\s/
# whitespace stuff
when "("
# open paren stuff
when ")"
# close paren stuff
else
# default stuff
end
end
end
And now I'm converting it to Rust:
fn rd_tree(chars: std::str::Chars) {
while let Some(c) = chars.next() {
if c.is_whitespace() {
// whitespace stuff
} else if c == '(' {
// open paren stuff
} else if c == ')' {
// close paren stuff
} else {
// default stuff
}
}
}
I resorted to using an if, else-if chain because as far as I can tell, Rust's match feature is limited to destructuring, enums, and type patterns. Is there a way to match on regexes or boolean functions? If not, is there a more idiomatic pattern here than if, else-if? I expect the logic to have more branches in the future and I want it to stay neat.
Not yet. The match patterns must be composed of things that can be statically verified by the compiler.
However, you can use a match guard:
fn rd_tree(chars: std::str::Chars) {
while let Some(c) = chars.next() {
match c {
c if c.is_whitespace() => {}
'(' => {}
')' => {}
_ => {}
}
}
}
A match guard allows you to run a function against whatever the pattern matched.
In the future, constant evaluation may be improved to allow calling functions in place of a pattern:
#[derive(PartialEq, Eq)]
struct Foo {
f: usize,
g: usize,
}
impl Foo {
const fn repeated(x: usize) -> Self {
Foo { f: x, g: x }
}
}
fn main() {
let f = Foo { f: 0, g: 1 };
match f {
const { Foo::repeated(22) } => println!("hi"),
_ => println!("1"),
}
}
This work is tracked in issue #57240. RFC 2920 "const expressions and patterns" (and its tracking issue #76001) are also relevant.
It's not immediately obvious to me how this would work with your exact example or a regex without a substantial amount of effort though.

Resources