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!()
}
Related
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
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
I am confused about the Some(T) keyword.
I want to check for two variables, if the value is defined (not None). If that is the case, the value of this variables is processed.
I know the match pattern which works like this:
match value {
Some(val) => println!("{}", val),
None => return false,
}
If I use this pattern, it will get very messy:
match param {
Some(par) => {
match value {
Some(val) => {
//process
},
None => return false,
}
},
None => return false,
}
This can't be the right solution.
The is a possibility, to ask if the param and value is_some() That would effect code like that:
if param.is_some() && value.is_some() {
//process
}
But if I do it like that, I always have to unwrap param and value to access the values.
I thought about something like this to avoid that. But this code does not work:
if param == Some(par) && value == Some(val) {
//process
}
The idea is that the values are accessible by par and val like they are in the match version.
Is there any solution to do something like this?
If I have several Option values to match, I match on a tuple of the values:
enum Color {
Red,
Blue,
Green,
}
fn foo(a: Option<Color>, b: Option<i32>) {
match (a, b) {
(Some(Color::Blue), Some(n)) if n > 10 => println!("Blue large number"),
(Some(Color::Red), _) => println!("Red number"),
_ => (),
}
}
fn main() {
foo(Some(Color::Blue), None);
foo(Some(Color::Blue), Some(20));
}
This allows me to match the combinations that are interesting, and discard the rest (or return false, if that is what you want to do).
If your function is processing multiple Option values, and would like to discard them if they're not Some, your function could return an Option itself:
fn foo(param: Option<usize>, value: Option<usize>) -> Option<usize> {
let result = param? + value?;
Some(result)
}
This will short-circuit the function in case there's a None value stored in either param or value.
Please read the book for more information on the ? operator.
If your function can't return an Option, you can still get away with destructuring using if let or match:
let x = if let (Some(p), Some(v)) = (param, value) {
p + v
} else {
return 0;
}
let x = match (param, value) {
(Some(p), Some(v)) => p + v,
(Some(p), _) => p,
(_, Some(v) => v,
_ => return 0,
}
Please read What is this question mark operator about? for more information on the ? operator
Please read this chapter in Rust by Example for more information on destructuring multiple things at once
There's a couple more alternatives not yet listed:
If you're willing to use experimental features (and hence the nightly compiler) you can use a try block as an alternative of extracting a function.
#![feature(try_blocks)]
fn main() {
let par: Option<f32> = Some(1.0f32);
let value: Option<f32> = Some(2.0f32);
let x: Option<f32> = try { par? + value? };
println!("{:?}", x);
}
Another alternative is to use map which only applies if the value is not None
let x: Option<f32> = par.map(|p| value.map(|v| p + v));
the question is about the default case.
Let's consider the following code:
fn func(x: i64) {
match x {
0 => println!("Zero"),
1 => println!("One"),
_ => {
//How to get the value here not via repeating the matched expression ?
}
};
}
Assuming you don't want to repeat the expression because it's more complex than just a variable, you can bind it to a variable:
fn func(x: i64) {
match <some complex expression> {
0 => println!("Zero"),
1 => println!("One"),
y => {
// you can use y here
}
};
}
This also works as a default case, because a variable pattern matches everything just like _ does.
_ is useful exactly when you don't want to use the value.
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");
}
};