Daniel Trebbien
Daniel Trebbien

Reputation: 39208

How to access fields of a value bound to a variable in a match expression?

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

Answers (2)

nielsle
nielsle

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

Shepmaster
Shepmaster

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 type Foo, 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

Related Questions