6
votes

I tried my hands at Rust for the first time today (writing a XML tokenizer), and naturally don’t understand everything:

I have a struct with field that can take an enum value:

enum State { Outside, InATag(~str) }
struct Tokenizer { state: State }

In a impl Tokenizer, I want to match on the current state, and change it in some cases, however this always gives a use of moved value error.

H to access and/or declare the state field so that I can match on it and change its value inside a match branch?


Sorry for the confusion, I meant to change the Tokenizer’s state field, not the state’s String field!

match self.state {
    InATag(name) => self.state = Outside,
    Outside => ()
}
2
what version of Rust are you using ? - barjak
The Rust language is changing fast, syntax and semantics are not fixed, yet. In particular, I'm pretty sure that the behavior of match changed recently (or will change, maybe in 0.7). - barjak

2 Answers

5
votes

Without a more concrete example, it is hard to tell whether this would solve your problem, but you can use ref within a match pattern to make a reference to the matched substructure, and you can use ref mut to make that reference mutable.

So, in your example:

enum State { Outside, InATag(~str) }
struct Tokenizer { state: State }

fn main() {
    let mut t = Tokenizer { state: InATag(~"foo") };
    match t.state {
         InATag(ref mut _s)  => { *_s = ~"bar"; }
         Outside => { /* impossible */ }
    }
    io::println(fmt!("Hello World: %?", t));
}

or if you need to match other parts of the Tokenizer state, this works too:

fn main() {
    let mut t = Tokenizer { state: InATag(~"foo") };
    match t {
        Tokenizer { state: InATag(ref mut _s) } => { *_s = ~"bar"; }
        Tokenizer { state: Outside } => { /* impossible */ }
    }
    io::println(fmt!("Hello World: %?", t));
}

Note that when doing this sort of code, it can be pretty easy to inadvertently run into borrow-check violations due to aliasing. For example, here is a relatively small change to the second example above that won't compile:

fn main() {
    let mut t = Tokenizer { state: InATag(~"foo") };
    match &t {
        &Tokenizer { state: InATag(ref mut _s) } => { *_s = ~"bar"; }
        &Tokenizer { state: Outside } => { /* impossible */ }
    }
    io::println(fmt!("Hello World: %?", t));
}

causes the following message from rustc:

/tmp/m.rs:7:35: 7:46 error: illegal borrow: creating mutable alias to enum content
/tmp/m.rs:7         &Tokenizer { state: InATag(ref mut _s) } => { *_s = ~"bar"; }
                                           ^~~~~~~~~~~
error: aborting due to previous error

because you do not want an outstanding borrow &t while you are also creating mutable aliases to the internals of t

2
votes

Okay, with the clarified variant of the question, here is my revised answer:

enum State { Outside, InATag(~str) }
struct Tokenizer { state: State }

impl Tokenizer {
    fn toggle(&mut self) {
        match self {
            &Tokenizer { state: InATag(*) } => { self.state = Outside }
            &Tokenizer { state: Outside }   => { self.state = InATag(~"baz") }
        }
    }
}

fn main() {
    let mut t1 = Tokenizer { state: InATag(~"foo") };
    match t1 {
        Tokenizer { state: InATag(*) } => { t1.state = Outside }
        Tokenizer { state: Outside } => { /* impossible */ }
    }
    io::println(fmt!("Hello t1: %?", t1));
    let mut t2 = Tokenizer { state: InATag(~"bar") };
    t2.toggle();
    io::println(fmt!("World t2: %?", t2));
}

I admit, I actually did not expect it to be as easy as the above, and I can easily believe that small changes to the above code could cause it to start failing to borrow-check. But without a more fleshed out example from the asker, it is hard to tell whether the above code would suit his purposes or not.

Oh, here's the output when I compile and run the code:

% rustc /tmp/m.rs
warning: no debug symbols in executable (-arch x86_64)
% /tmp/m
Hello t1: {state: Outside}
World t2: {state: Outside}
%