Is there a more ergonomic pattern for types that "build up?"

60 points Uxugin 43 comments

A few times, I've found myself needing a type with "stages" that add upon eachother, usually when dealing with some kind of graph structure. One way of doing this is:

enum Thing {
    Stage1 { a: u8 },
    Stage2 { a: u8, b: u8 },
    Stage3 { a: u8, b: u8, c: u8, d: u8 },
}

But that requires some ugly pattern matching when trying to write code that works for multiple stages:

match thing {
    Stage1 { .. } => panic!("stage 2 or 3 required"),
    Stage2 { a, b } | Stage3 { a, b, .. } => todo!(),
}

It also doesn't work well when switching stages because fields need to be moved into the other variant rather than just staying in place. This isn't a problem for this example, but it is in a real usecase where some fields are not Copy.

The other way I have found is:

struct Stage3 {
    c: u8,
    d: u8,
}
struct Stage2 {
    b: u8,
    stage_3: Option<Stage3>,
}
struct Thing {
    a: u8,
    stage_2: Option<Stage2>,
}

This avoids some of the problems of the other way, but it's also pretty ugly to work with, and it requires defining a lot of extra structs, probably in a dedicated module, depending on how many stages you need.

Is there a nicer way of doing this?

Edit: To clarify, there is a constraint that Thing has the same type regardless of what stage it's in. It needs to be able to be stored generically over its stage. This is why the builder/typestate pattern doesn't work here.