This question already has answers here:
How do I match based on a dynamic variable?
(4 answers)
Closed 6 months ago.
I am trying to match an enum struct. This enum has different variants with different (or none) fields, such as:
enum Enum {
One {n : usize},
Two ,
}
The match block that I am trying to achieve looks like:
match m {
Enum::One{n: special_value} => {
println!("it is special! {}", special);
}
Enum::One{n} => {
println!("not special: {}", n);
}
Enum::Two => {
println!("not special");
}
}
If I define let m = Enum::One{n: 3} and let special_value = 8 the code prints it is special! 3. This should print not special: 3.
If I change the match block to (note that I changed the variable special_value variable to the literal 8:
match m {
Enum::One { n: 8 } => {
println!("it is special!");
}
Enum::One { n } => {
println!("not special: {}", n);
}
Enum::Two => {
println!("not special");
}
}
Now defining let m = Enum::One { n: 3 } it prints as desired not special: 3.
In my actual project case, I cannot use a literal to do the match the value comes from the function call as a variable.
For this porpouse I can use this match block (note the special case has been removed and replaced by an if clause inside the general case) :
match m {
Enum::One { n } => {
if n == special_value {
println!("it is special! {}", special_value);
} else {
println!("not special: {}", n);
}
}
Enum::Two => {
println!("not special");
}
}
Is it possible to use a variable inside the match, or am I forced to use the if clause? What is the reason?
yes. you can add if statements on the match arm without creating if block inside match arm block
does this works for you?
enum Enum {
One {
n: usize
},
Two
}
fn main() {
let special_value = 123;
let m = Enum::One { n: 123 };
match m {
Enum::One { n } if n == special_value => println!("it is special! {}", special_value),
Enum::One { n } => println!("not special: {}", n),
Enum::Two => {
println!("not special");
}
}
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=acfaae3976e7ca9fcf096c635a4daf98
works for me.
Related
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");
}
As seen in the code below, each iteration of the loop defines its own instance of Foo, so I don't see how it could be "moved" in a "previous iteration of loop" when there is a new Foo per loop.
How do I make the error go away?
fn main() {
for i in 0..2 {
let vector: Foo;
// ------ move occurs because `vector` has type `Foo`, which does not implement the `Copy` trait
if i == 0 {
vector = Foo::Bar(vec![1_f32]);
} else if i == 1 {
vector = Foo::Baz(vec![1_u16]);
}
// - value moved here, in previous iteration of loop
println!("{}", vector.len());
// ^^^^^^ value used here after move
}
}
enum Foo {
Bar(Vec<f32>),
Baz(Vec<u16>)
}
impl Foo {
pub fn len(self) -> usize {
match self {
Foo::Bar(vector) => vector.len(),
Foo::Baz(vector) => vector.len(),
#[allow(unreachable_patterns)]
_ => unreachable!()
}
}
}
Your if-else chain is not exhaustive:
fn main() {
for i in 0..2 {
let vector: Foo;
if i == 0 {
vector = Foo::Bar(vec![1_f32]);
} else if i == 1 {
vector = Foo::Baz(vec![1_u16]);
}
// What should happen if `i` is not 0 or 1 ?
// Then the app will try to use an uninitialized variable
// The compiler cannot figure out that it is impossible
// with the current input
println!("{}", vector.len());
}
}
So you have to add an else statement for the case when i is not 0 or 1:
fn main() {
for i in 0..2 {
let vector: Foo;
if i == 0 {
vector = Foo::Bar(vec![1_f32]);
} else if i == 1 {
vector = Foo::Baz(vec![1_u16]);
} else {
unreachable!();
}
println!("{}", vector.len());
}
}
Or better use a match statement (as in your own answer) because it's much cleaner and easier to read:
fn main() {
for i in 0..2 {
let vector: Foo = match i {
0 => Foo::Bar(vec![1_f32]),
1 => Foo::Baz(vec![1_u16]),
_ => unreachable!()
};
println!("{}", vector.len());
}
}
Relevant issue (thanks #nneonneo): https://github.com/rust-lang/rust/issues/72649
By using a match statement I managed to make the errors go away. I don't know why this works and the previous code didn't:
fn main() {
for i in 0..2 {
let vector: Foo = match i {
0 => Foo::Bar(vec![1_f32]),
1 => Foo::Baz(vec![1_u16]),
_ => unreachable!()
};
println!("{}", vector.len());
}
}
I want to do something along the lines of
enum A {
Type1 {
s: String
// ... some more fields
}
// ... some more variants
}
impl A {
fn consume(self) { println!("Consumed!"); }
}
fn fails() {
let b = A::Type1 { s: String::from("Arbitrary string") };
match b {
A::Type1 {
s, // (value moved here)
// ... more fields
} => {
let l = s.len(); // Something using the field from the enum
if l > 3 {
s.into_bytes(); // do something that requires ownership of s
} else {
b.consume(); // Value used here after move
}
}
// ... more cases
}
}
However, because I destructure b in the match case, I don't have access to it within the body of the match.
I can reconstruct b from the fields, but when b has lots of fields this is obviously not ideal. Is there any way to get around this issue without having to rebuild b?
No, you (almost literally) cannot have your cake and eat it too. Once you have destructured the value, the value no longer exists.
When non-lexical lifetimes happens, you can use a combination of NLL and match guards to prevent taking ownership in the first place:
#![feature(nll)]
enum A {
Type1 { s: String },
}
impl A {
fn consume(self) {
println!("Consumed!");
}
}
fn main() {
let b = A::Type1 {
s: String::from("Arbitrary string"),
};
match b {
A::Type1 { ref s } if s.len() <= 3 => {
b.consume();
}
A::Type1 { s } => {
s.into_bytes();
}
}
}
In my code below I find that the code in match_num_works() has a certain elegance. I would like to write a String match with a similar formulation but cannot get it to work. I end up with match_text_works() which is less elegant.
struct FooNum {
number: i32,
}
// Elegant
fn match_num_works(foo_num: &FooNum) {
match foo_num {
&FooNum { number: 1 } => (),
_ => (),
}
}
struct FooText {
text: String,
}
// Clunky
fn match_text_works(foo_text: &FooText) {
match foo_text {
&FooText { ref text } => {
if text == "pattern" {
} else {
}
}
}
}
// Possible?
fn match_text_fails(foo_text: &FooText) {
match foo_text {
&FooText { text: "pattern" } => (),
_ => (),
}
}
Its probably not "elegant" or any nicer.. but one option is to move the conditional into the match expression:
match foo_text {
&FooText { ref text } if text == "pattern" => (),
_ => ()
}
Working sample: Playpen link.
Note that your desired pattern would actually work with a &str. You can't directly pattern match a String because it's a more complex value that includes an unexposed internal buffer.
struct FooText<'a> {
text: &'a str,
_other: u32,
}
fn main() {
let foo = FooText { text: "foo", _other: 5 };
match foo {
FooText { text: "foo", .. } => println!("Match!"),
_ => println!("No match"),
}
}
Playground
I have this:
struct Test {
amount: f32
}
fn main() {
let amnt: String = "9.95".to_string();
let test = Test {
amount: match amnt.parse() {
Ok(num) => num.unwrap(),
Err(e) => 0f32
}
};
}
and it's causing an error:
error: the type of this value must be known in this context
Ok(num) => num.unwrap(),
^~~~~~~~~~~~
How do I cast num to fix this error?
As you're already pattern matching on Ok(), you don't need to call unwrap(); num is already of type f32.
This compiles fine:
struct Test {
amount: f32
}
fn main() {
let amnt: String = "9.95".to_string();
let test = Test {
amount: match amnt.parse() {
Ok(num) => num,
Err(e) => 0f32
}
};
}
You can also use Result::unwrap_or() instead:
Test {
amount: amnt.parse().unwrap_or(0.0)
}