0

I have written some serial protocol code:

#[derive(Debug, PartialEq, Eq)]
enum State {
    WAIT_DLE,
    WAIT_STX,
    WAIT_DLE_OR_BYTE { payload: Vec<u8> },
    WAIT_DLE_OR_ETX { payload: Vec<u8> },
    WAIT_CRC { payload: Vec<u8>, crc: u32, remaining: usize },
}

#[derive(Debug, PartialEq, Eq)]
struct Decoder {
    state: State,
    decoded: u64,
    unstuffing_errors: u64,
    crc_errors: u64
}

impl Decoder {
    fn new() -> Decoder {
        Decoder { 
            state: State::WAIT_DLE,
            decoded: 0,
            unstuffing_errors: 0,
            crc_errors: 0,        
        }
    }

    fn decode_byte(&mut self, byte: u8) -> Option<Vec<u8>> {
        self.state = match self.state {
            State::WAIT_DLE => match byte {
                DLE => State::WAIT_STX,
                _ => State::WAIT_DLE
            },
            State::WAIT_STX => match byte {
                STX => State::WAIT_DLE_OR_BYTE { payload: Vec::<u8>::new() },
                _ => State::WAIT_DLE
            },
            State::WAIT_DLE_OR_BYTE { mut payload } => match byte {
                DLE => State::WAIT_DLE_OR_ETX { payload },
                b => {
                    payload.push(b);
                    State::WAIT_DLE_OR_BYTE { payload }
                }
            },
            ...

The error is:

error[E0507]: cannot move out of `self.state.payload` as enum variant `WAIT_CRC` which is behind a mutable reference
  --> common/src/dlecrc32.rs:46:28
   |
46 |         self.state = match self.state {
   |                            ^^^^^^^^^^ help: consider borrowing here: `&self.state`
...
55 |             State::WAIT_DLE_OR_BYTE { mut payload } => match byte {
   |                                       ----------- data moved here
...
62 |             State::WAIT_DLE_OR_ETX { mut payload } => match byte {
   |                                      ----------- ...and here
...
73 |             State::WAIT_CRC { payload, mut crc, remaining } => {
   |                               ------- ...and here
   |
   = note: move occurs because these variables have types that don't implement the `Copy` trait

Its advice to use a &self.state reference is not helpful, as many paths need to rip the payload: Vec<u8> out of the old state and put it into the new state. (Copying the vector's contents would be wasteful, as its old owner is being dropped.)

I can fix the problem by using mem::swp() to temporarily put a dummy value into state while dismembering the old state:

fn decode_byte(&mut self, byte: u8) -> Option<Vec<u8>> {
    let mut swapped_state = State::WAIT_DLE;
    mem::swap(&mut self.state, &mut swapped_state);

    self.state = match swapped_state {

This is wasteful, as it is copying the bytes of self.state when that is not required.

Another unattractive solution is to make the State properties properties of the Decoder, turning the State enum into a C-style enum.

Is there a way to let the compiler accept that as I'm assigning to self.state, it's OK for me to dismember the old self.state?

fadedbee
  • 42,671
  • 44
  • 178
  • 308
  • `mem::replace()` (not `mem::swap()` is the correct way. But the compiler is also right: what if `payload.push()` panics? The value will be left uninitialized. – Chayim Friedman Jul 13 '23 at 12:54
  • @ChayimFriedman The compiler also complains about the non-mutable `State::WAIT_CRC { payload, mut crc, remaining } => {` so I'm guessing that its objecting to something other than the `payload.push()`. (Note: that `WAIT_CRC` branch is beyond the end of the code snippet.) – fadedbee Jul 17 '23 at 08:22
  • I didn't say it is concerned by that, it doesn't understand your code to that level of detail. But it is concerned with the language rules, and those are designed to prevent that. – Chayim Friedman Jul 17 '23 at 08:23
  • @ChayimFriedman Ah, thanks. – fadedbee Jul 17 '23 at 08:24

0 Answers0