Changing enum variant through a unique reference

I have the following code (which doesn't compile):

enum Symbol {
    Unknown(Box<str>),
    Defined(Box<str>, Definition),
}

struct Definition;

fn define(symbol: &mut Symbol, definition: Definition) {
    match symbol {
        Symbol::Unknown(ident) => {
            *symbol = Symbol::Defined(*ident, definition)
        }
        Symbol::Defined(_, _) => todo!()
    }
}

I understand why it doesn't compile, but is there a way to do what I'm trying to do without using Option or copying the string?

I suspect there’s a much easier way, but something along these lines can be made to work:

fn define(symbol: &mut Symbol, definition: Definition) {
    thread_local! {
       pub static TMP: Cell<Option<Box<str>>> = Cell::new(Some(String::from("").into()));
    }

    match symbol {
        Symbol::Unknown(_) => {
            // make a temporary replacement
            let mut tmp = Symbol::Unknown(TMP.with(|cell| cell.take()).take().unwrap());

            // put the real value in tmp
            std::mem::swap(&mut tmp, symbol);

            // build the new value
            let mut tmp = if let Symbol::Unknown(ident) = tmp {
                Symbol::Defined(ident, definition)
            } else { panic!() };

            // swap it back
            std::mem::swap(&mut tmp, symbol);

            // store our temporary string for next time
            if let Symbol::Unknown(ident) = tmp {
                TMP.with(|cell| cell.set(Some(ident)));
            } else { panic!() }
        },
        Symbol::Defined(_, _) => todo!()
    }
}
2 Likes

Typically might add another item to enum as temporary replacement.
You can get away with just using

std::mem::replace(ident, Box::default())

Thanks to power of zero there is no allocation. Expect (without looking) it all is optimized away too.

2 Likes

For cases where you can't create a cheap temporary replacement, see the replace_with crate.

1 Like