b0fh
b0fh

Reputation: 1735

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 ?

Upvotes: 2

Views: 599

Answers (1)

Boiethios
Boiethios

Reputation: 42759

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()
    }
}

Upvotes: 2

Related Questions