Reputation: 39208
I have code similar to:
use std::string::{String};
use std::vec::{Vec};
enum State {
A {
n: usize,
lines: Vec<String>,
},
B {
n: usize,
}
}
fn main() {
use State::*;
let lines = vec!["a", "b", "GO", "c", "GO", "d"];
let mut state = B { n: 0 };
for line in &lines {
state = match state {
A { n, lines } => {
if line == &"GO" {
B { n: n + 1 }
} else {
let mut new_lines = Vec::from(lines);
new_lines.push(line.to_string());
A { n: n, lines: new_lines }
}
},
B { n } => {
A { n: n, lines: vec![line.to_string()] }
},
};
}
let final_n = match state {
A { n, .. } => n,
B { n } => n,
};
println!("final_n = {}", final_n);
}
Rust Playground link: http://is.gd/0QTYaQ
(Note that this is a simplification of the actual code. See the first revision of this question for the full background.)
I want to avoid creating the new_lines
vector, so I tried binding the State::A
value to a variable and accessing the fields of the value like so:
s @ A { .. } => {
if line == &"GO" {
B { n: s.n + 1 }
} else {
s.lines.push(line.to_string());
s
}
},
However, this fails to compile:
ParseState_enum_test.rs:23:28: 23:31 error: attempted access of field `n` on type `State`, but no field with that name was found ParseState_enum_test.rs:23 B { n: s.n + 1 } ^~~ ParseState_enum_test.rs:19:5: 33:6 note: in this expansion of for loop expansion ParseState_enum_test.rs:25:21: 25:28 error: attempted access of field `lines` on type `State`, but no field with that name was found ParseState_enum_test.rs:25 s.lines.push(line.to_string()); ^~~~~~~ ParseState_enum_test.rs:19:5: 33:6 note: in this expansion of for loop expansion error: aborting due to 2 previous errors
How do I access the fields of the value bound to the variable?
EDIT: I am aware of ref mut
in pattern binding, but I don't think that this is a good solution in my case. If I use ref mut
, then I need to create a clone of the vector because this code does not compile:
A { n, ref mut lines } => {
if line == &"GO" {
B { n: n + 1 }
} else {
lines.push(line.to_string());
A {
n: n,
lines: lines, // error: mismatched types
}
}
},
Upvotes: 0
Views: 207
Reputation: 381
The following seems to work. Does it solve the problem?
let new_state = match state {
B {n} => A { n: n, lines: vec![line.to_string()] },
A {n, mut lines} => {
match *line {
"GO" => B { n: n + 1 },
_ => {
lines.push(line.to_string());
A{ n:n, lines: lines}
}
}
}
};
state = new_state
https://play.rust-lang.org/?gist=4fa712834999e45ccd4d&version=stable
Upvotes: 4
Reputation: 430791
Let's look at a much simpler version of your issue:
enum Foo {
Alpha { score: u8 },
Beta { lives_left: u8 },
}
fn main() {
let the_value = Foo::Alpha { score: 42 };
match the_value {
alpha_only @ Alpha => println!("Score is {}", alpha_only.score),
_ => println!("Dunno what to do!"),
}
}
The problem is that enum variants are not standalone types. That is, there is no way to have a variable of type Foo::Alpha
; you can only have it be the type Foo
. You can see this in the error message:
attempted access of field
score
on typeFoo
, but no field with that name was found
When you use @
to bind the entire pattern, you can only know that you are getting something of type Foo
.
The normal way of dealing with this is to bind to a component of the item using ref
:
match the_value {
Foo::Alpha { ref score } => println!("Score is {}", score),
_ => println!("Dunno what to do!"),
}
And if you need to mutate the value, use ref mut
:
match the_value {
Foo::Alpha { ref mut score } => {
*score += 1;
println!("Score is {}", score)
},
_ => println!("Dunno what to do!"),
}
Of if you can consume the value, you don't need ref
:
let the_value = Foo::Alpha { score: 42 };
let new_value = match the_value {
Foo::Alpha { score } => Foo::Alpha { score: score + 1 },
Foo::Beta { lives_left } => Foo::Alpha { score: lives_left * 2 },
};
Upvotes: 2