Replace enum variant inside match

How to match the enum and replace it in some branches using some parameters in the new variant?

This is example is not compiling with "cannot move out of `self.state` as enum variant `State0` which is behind a mutable reference". I understand why this code is wrong, but how to achieve the desired result? I tried matching against a reference, but then I just get another compilation error.

struct Foo {}

enum State {
    State0(Foo),
    State1(Foo),
    State2,
}

struct Bar {
    state: State,
}

impl Bar {
    fn process(&mut self) {
        match self.state {
            State::State0(foo) => {
                println!("state 0");
                self.state = State::State1(foo);
            }
            State::State1(foo) => {
                println!("state 1");
            },
            State::State2 => {
                println!("state 2");
            }
        }
    }
}

fn main() {
    let mut t = Bar {state: State::State0(Foo{})};
    t.process();
    t.process();
}

How about not mutating an instance of Bar, but moving it through Bar::process?

struct Foo {}

enum State {
    State0(Foo),
    State1(Foo),
    State2,
}

struct Bar {
    state: State,
}

impl Bar {
    fn process(mut self) -> Self {
        match self.state {
            State::State0(foo) => {
                println!("state 0");
                self.state = State::State1(foo);
            }
            State::State1(_) => {
                println!("state 1");
            }
            State::State2 => {
                println!("state 2");
            }
        }

        self
    }
}

fn main() {
    let t = Bar {
        state: State::State0(Foo {}),
    };
    
    let t = t.process();
    t.process();
}

Playground.

2 Likes

Wow. I'm still not used to such things. So we give ownership to the method and then the method returns it.
Actually, it's still mutating the same instance, but through giving away ownership and not through mutable reference.

What if that method has to return Result?

Ok, that's actually easy:

fn process(self) -> (Self, Result<()>) {...}

That will break usage of ? operator, but :man_shrugging:

I'd say

fn process(self) -> Result<Self, Error> { ... }

is easier and more idiomatic :slightly_smiling_face:

1 Like

That way in case of an error we will lose Bar instance.

If you want to stick to your mutating version, you can do so by matching a reference of self.state and cloning foo from State0 to State1. We have to move foo somehow from one state to the other and that is only possible with ownership (like I showed in my example above), or by creating a new instance of Foo, avoiding moving out of a shared reference. Playground.

Foo is not clonable. It's actually a (tx,rx) of mpsc channels.

One more possibility is to replace self.state with some temporary state. But that makes it hard to return errors from inside the match statement.
playground link

use std::mem;

#[derive(Debug)]
struct Foo {}

enum State {
    State0(Foo),
    State1(Foo),
    State2,
}

struct Bar {
    state: State,
}

impl Bar {
    fn process(&mut self) {
        self.state = match mem::replace(&mut self.state, State::State2) {
            State::State0(foo) => {
                println!("state 0 {:?}", foo);
                State::State1(foo)
            }
            State::State1(foo) => {
            // unfortunately it is not possible to use `prev_state @` 
            // if I want to access foo
                println!("state 1 {:?}", foo);
                State::State1(foo)
            },
            prev_state @ State::State2 => {
                println!("state 2");
                prev_state
            }
        }
    }
}

fn main() {
    let mut t = Bar {state: State::State0(Foo{})};
    t.process();
    t.process();
}

in that case you can use

fn process(self) -> Result<Self, (Self, Error)> { ... }

3 Likes

The common idiom for that is to have Self as field of Error.

2 Likes

So here is the final version of what I'm going to implement. I really like the idea of ownership transferred to the method and back, but this variant looks cleaner to me.
playground

#[derive(Debug)]
struct RxTxPair {}

#[derive(Debug)]
enum WsState {
    Connecting(RxTxPair),
    Connected(RxTxPair),
    Disconnected,
    Undefined,
}

impl WsState {
    pub fn set_connected(&mut self) -> Result<(), String> {
        let old_state = std::mem::replace(self, WsState::Undefined);
        let WsState::Connecting(rxtx) = old_state else {
            *self = old_state;
            return Err("Bad State".to_string());
        };
        *self = WsState::Connected(rxtx);
        Ok(())
    }
}

struct WebSocket {
    state: WsState,
}

impl WebSocket {
    fn process(&mut self) -> Result<(), String> {
        match &self.state {
            WsState::Connecting(rxtx) => {
                println!("Connecting {rxtx:?}");
                self.state.set_connected()?;
            },
            WsState::Connected(rxtx) => {
                println!("Connected {rxtx:?}");
                self.state = WsState::Disconnected;
            },
            WsState::Disconnected => {
                println!("Disconnected")
            }
            WsState::Undefined => {
                unreachable!()
            }
        }
        Ok(())
    }
}

fn main() -> Result<(), String> {
    let mut t = WebSocket {state: WsState::Connecting(RxTxPair{})};
    t.process()?;
    t.process()?;
    t.process()?;
    Ok(())
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.