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

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

Related

Match an enum that takes a vector in one of its fields

I'm trying to match an event to a start of a heading with pulldown_cmark.
Let's say I have this:
enum MyEvent<'a> {
Start(MyTag<'a>),
End(MyTag<'a>),
}
enum MyTag<'a> {
Heading(SomeHeadingLevelEnum, Option<&'a str>, Vec<&'a str>,
Paragraph,
}
fn main() {
// Simplified the enums for the sake of the example
let event = Start(Heading(Level::H1, None, vec![]));
}
How can I match the event to a type? Tried with:
fn main() {
// ...
match event {
Start(Heading(H1, None, [])) => println!("a heading!"),
_ => println!("yeah not a type I know for sure"),
}
}
but got:
Start(Heading(H1, None, [])) => println!("a H1!")
^^ pattern cannot match with input type `Vec<&str>`
I tried with other approaches, as using vec![], &[&str], Vec::new(""), but got other errors as well.
You can't have an expression in a match expression like that, it takes a variable there instead, which it will populate with a successful match if one exists. If you want it only to succeed when the Vec is empty, then you can use a match guard to check it:
match event {
Start(Heading(H1, None, v)) if v.is_empty() => println!("a heading!"),
_ => println!("yeah not a type I know for sure"),
}
Side note: instead of having &str in your enums like that, you may want to use a String, so that you don't have to propagate those pesky lifetime annotations everywhere.
You can't match a Vec of specific length. As a pattern, [] matches an array of length zero, which is not the same as a Vec.
Instead, you can match the variable and add a guard for it being empty:
match event {
Start(Heading(H1, None, lines)) if lines.is_empty() => println!("a heading!"),
_ => println!("yeah not a type I know for sure"),
}
Sadly it do not work like that, you would have to match it and then use a guard in the match branch:
match &event {
MyEvent::Start(MyTag::Heading(Heading::H1, None, v)) if v.is_empty() => println!("a heading!"),
_ => println!("yeah not a type I know for sure"),
}
Playground

Bind variable to literal in Rust pattern matching

I have a state machine written in Rust which needs to perform the same action for two states.
Both states are variants of an enum but contain a different number of elements.
match self.state {
RunState::ACCUMULATE(byte, count) | RunState::ESCAPE(count) => todo!(),
_ => todo!()
}
The example is invalid source code because byte is not bound in all patterns.
This could be solved by binding byte to a literal zero in the second pattern but i don't know how to archive that.
I`am currently matching both patterns separately which leads to code duplication which i would like to avoid.
Thank you for your attention.
Patterns are designed to match by structure and bind variables based on the value being matched. It is not designed to introduce arbitrary bindings. You should use separate match arms and perhaps use a helper function if you're concerned about duplication:
match self.state {
RunState::ACCUMULATE(byte, count) => helper(byte, count),
RunState::ESCAPE(count) => helper(0, count),
...
}
Or you can introduce a function for your enum that returns the needed values like:
impl RunState {
fn to_parts(&self) -> (u8, usize) { // or Option<(u8, usize)> if its not applicable to all
match self.state {
RunState::ACCUMULATE(byte, count) => (byte, count),
RunState::ESCAPE(count) => (0, count),
...
}
}
}
You can solve your issue by not binding byte in any branch:
match self.state {
RunState::ACCUMULATE(_, count) | RunState::ESCAPE(count) => todo!(),
_ => todo!()
}
If you need byte in the ACCUMULATE case, then the code isn't strictly speaking the same for both, but this can be accomplished with an extra step to extract byte:
match self.state {
RunState::ACCUMULATE(_, count) | RunState::ESCAPE(count) => {
let byte = if let RunState::ACCUMULATE (byte, _) = self.state { byte } else { 0 };
todo!()
},
_ => todo!()
}

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

Match with a variable vs match with a placeholder?

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.

Type annotation in a pattern match in Rust?

I'm digging into Rust, specifically into gracefully handling errors, but I'm having a little trouble with type inference.
extern crate mysql;
use mysql as my;
fn main() {
my_test();
}
fn my_test() -> Result<(), my::Error> {
let pool = try!(my::Pool::new(""));
let res = try!(pool.prep_exec("select 1 as count", ()));
for rows in res {
let row: my::Row = try!(rows);
match row.take("count") {
None => (),
Some(i) => println!("{:?}", i),
};
}
Ok(())
}
which leads to
src/bin/main.rs:86:12: 86:13 error: unable to infer enough type information about _; type annotations or generic parameter binding required [E0282]
Unfortunately the docs in that crate use unwrap a lot, which does not help me. In Haskell, I would do something like println!("{:?}", i :: i32), but I can't figure out how to do it in Rust. I've tried various ways to cast row.take, but I've haven't had any luck. I'd love to see a variety of ways in which I could have structured this code, if there is a more idiomatic way of going about it.
Looking at Row::take documentation we can see two types parameter T and I. The type I is inferred from the "count" argument, the type T is used for the return type. We have two options to specify the return type, explicit in the method call, or implicit in type of a variable (like you did with row):
fn my_test() -> Result<(), my::Error> {
let pool = try!(my::Pool::new(""));
let res = try!(pool.prep_exec("select 1 as count", ()));
for rows in res {
let mut row: my::Row = try!(rows);
// specify type T explicitly, let type I to be inferred
match row.take::<i32, _>("count") {
None => (),
Some(i) => println!("{:?}", i),
};
// or
let s: Option<i32> = row.take("count");
}
Ok(())
}
The type ascription RFC proposes a syntax (similar to the Haskell example) for annotating a sub-expression with a type.
The types can be annotated on the Option<T> or Result<T, E> variants in the match pattern.
For Option,
match row.take("count") {
None => (),
Some::<i32>(i) => println!("{:?}", i),
}
Or if your function returns a Result,
match row.take("count") {
Err(e) => panic!("{:?}", e),
Ok::<i32, _>(i) => println!("{:?}", i),
}

Resources