Can I switch options to the value of a mutable enumeration link?

Is it possible to switch options to the value of a mutable link ( &mut E<T> ) without additional restrictions on T and not resort to unsafe code?

That is, given the enumeration:

 enum E<T> { VariantA(T), VariantB(T) } 

What is the correct way to write:

 let x: E<???> = E::VariantA(??); change_to_variant_b(&mut x); assert_eq!(x, E::VariantB(??)); 
+8
enums rust
source share
3 answers

I'm going to go on a limb and say no .


Perhaps only with a slight signature change:

 fn change_to_variant_b<T>(e: E<T>) -> E<T> { match e { E::VariantA(t) => E::VariantB(t), E::VariantB(t) => E::VariantB(t), } } 

It is possible to use unsafe :

 fn change_to_variant_b<T>(e: &mut E<T>) { use std::ptr; unsafe { match ptr::read(e as *const _) { E::VariantA(t) => ptr::write(e as *mut _, E::VariantB(t)), E::VariantB(t) => ptr::write(e as *mut _, E::VariantB(t)), } } } 

This is possible with additional restrictions ( Default or Clone ):

 fn change_to_variant_b<T: Default>(e: &mut E<T>) { match std::mem::replace(e, E::VariantA(T::default())) { E::VariantA(t) => e = E::VariantB(t), E::VariantB(t) => e = E::VariantB(t), } } 
+7
source share

As an alternative to this particular case, you can consider moving to a structure using T and a simple enumeration:

 struct Outer<T> { val: T, kind: Inner, } impl<T> Outer<T> { fn promote_to_b(&mut self) { self.kind.promote_to_b() } } enum Inner { VariantA, VariantB, } impl Inner { fn promote_to_b(&mut self) { if let Inner::VariantA = *self { *self = Inner::VariantB; } } } 
+1
source share

Is it possible to switch options to the value of a mutable link

As Mattiu M. said , the general answer is no. The reason is that this will cause the enumeration to be in an undefined state, which will allow access to undefined memory, which will violate Rust's security guarantees. As an example, let's assume that this code compiled without errors:

 impl<T> E<T> { fn promote_to_b(&mut self) { if let E::VariantA(val) = *self { // Things happen *self = E::VariantB(val); } } } 

The problem is that you moved the value from self to val , what should happen to the memory representing T inside self ?

If we copied the bits and then there was a panic in β€œThings happen”, the destructor for val and T inside self will execute, but since they point to the same data, this will lead to double free.

If we did not copy the bits, then you could not safely access the val inside the "Things happen", which would be utmost useful.


The cost solution works because the compiler can keep track of who should call the destructor: the function itself. When you are inside a function, the compiler knows what specific lines may be needed to free the value and properly names them in case of panic.

The Clone or Default solution works because you never move a value from the original enumeration. Instead, you can replace the original enumeration with a dummy value and take ownership of the original (using Default ) or duplicate the entire original value (using Clone ).


replace_with RFC (# 1736) suggested adding a method that would allow this to work, ensuring that the correct memory semantics were confirmed, but the RFC was not accepted.

0
source share

All Articles