rustphantom-types

Is there a safe, ergonomic way to change a phantom type in a complex struct?


Suppose we define a generic struct, with many fields, representing a type-safe state machine using a phantom type:

struct Foo<State> {
    a: A,
    b: B,
    c: C,
    //...
    state: PhantomData<State>,
}

We can then write a type-safe state transition:

impl Foo<SourceState> {
    fn transition(self, extra: X) -> Foo<DestinationState> {
        let Foo {a, b, c, state: _} = self;
        // do lots of stuff
        Foo { a, b, c, state: PhantomData } 
    }
}

But we need to awkwardly unpack every field and re-pack in in a different structure.

We could also use mem::transmute, although my understanding is that different monomorphizations of the same struct are not guaranteed to have the same memory layout.

I hoped that Foo { state: PhantomData, ..self } would work; alas, it fails to compile.

Is there any canonical, ergonomic, safe way to write this ?


Solution

  • There is no way to do that in a straight forward manner, because they are 2 different types: that's the whole point of your code, actually. To simplify it, I'd do that in 2 steps, with a generic transition being the 2nd one:

    use core::marker::PhantomData;
    
    struct Foo<State> {
        a: i32,
        b: i32,
        c: i32,
        //...
        state: PhantomData<State>,
    }
    
    struct SourceState;
    struct DestinationState;
    
    impl<Src> Foo<Src> {
        fn transition<Dest>(self) -> Foo<Dest> {
            let Foo {a, b, c, state: _} = self;
    
            Foo { a, b, c, state: PhantomData } 
        }
    }
    
    impl Foo<SourceState> {
        fn to_destination_state(mut self, extra: ()) -> Foo<DestinationState> {
            // Do whatever you want with self
    
            self.transition()
        }
    }
    

    Alternatively, you can abstract the fact that you have a state:

    mod stateful {
        use core::marker::PhantomData;
    
        pub struct Stateful<T, State> {
            pub data: T,
            state: PhantomData<State>,
        }
    
        impl<T, SrcState> Stateful<T, SrcState> {
            pub fn transform<DestState>(self) -> Stateful<T, DestState> {
                let Stateful { data, state: _ } = self;
    
                Stateful {
                    data,
                    state: Default::default(),
                }
            }
        }
    }
    
    struct Data {
        a: i32,
        b: i32,
        c: i32,
    }
    
    struct SourceState;
    struct DestinationState;
    
    type Foo<State> = stateful::Stateful<Data, State>;
    
    impl Foo<SourceState> {
        fn to_destination_state(mut self, extra: ()) -> Foo<DestinationState> {
            // Do whatever you want with self.data
    
            self.transform()
        }
    }