AlwaysSome and AlwaysNone Option

I have an enum like this:

enum Enum {
    VariantA {option: Option<TypeA>},
    VariantB {option: Option<TypeB>},
    VariantC {option: Option<TypeC>},
}

I would like to derive two types from this:

  • a type where option is always Some for all variants, and
  • a type where option is always None for all variants.

I see two possibilities to approach this. First, this could be done via macro. Then the option field could be left out completely for the AlwaysNone type. I have no idea how to create proc-macros though, so is there a crate that does this? Creating a copy of an enum type while leaving out one field for each variant?

Otherwise, would this be possible via the type system? By somehow parameterising the Enum with some type parameter, that decides if option is a special zero-sized type, or some other custom type.

For example, we could have Enum<AlwaysSome>, Enum<AlwaysNone> and a special type StaticOption<Flag, Type>. Then StaticOption<AlwaysSome, Type> would hold a value of type Type, whereas StaticOption<AlwaysNone, Type> would be a zero-sized type without holding a value of type Type. Then Enum would look as follows:

enum Enum<Flag> {
    VariantA {option: StaticOption<Flag, TypeA>},
    VariantB {option: StaticOption<Flag, TypeB>},
    VariantC {option: StaticOption<Flag, TypeC>},
}

Is it possible to build such a StaticOption type in Rust?

Sounds like a XY problem. Could you describe your actual goal with this? There might be an easier way.

3 Likes
enum Enum {
    A(A),
    B(B),
    C(C),
}

enum EnumNone {
    A,
    B,
    C,
}

Is roughly what you'll end up with, minus overcomplicated types.

Have you considered simply using:

Option<Enum>

As Segment7163 says, it really does sound like an XY problem

it's possible to "select" different types based on a tag type or a selector type with GATs:

trait Selector {
	type Selected<T>;
}

type StaticOption<S, T> = <S as Selector>::Selected<T>;

struct AlwaysSome;
struct AlwaysNone;

impl Selector for AlwaysSome {
	type Selected<T> = T;
}

impl Selector for AlwaysNone {
	type Selected<T> = ();
}

but it's not as useful as you might think, you need a trait bound, and you may only use it generically.

again, this is very likely an XY problem, please describe your actual problem, not what you thought the solution should look like.

2 Likes

Great thanks, this seems to work flawlessly!

I played a bit with it, and embedded it in my example:

use core::fmt::Debug;

trait Selector {
	type Selected<T: Debug>: Debug;
}

type StaticOption<S, T> = <S as Selector>::Selected<T>;

#[derive(Debug)]
struct AlwaysSome;
#[derive(Debug)]
struct AlwaysNone;

impl Selector for AlwaysSome {
	type Selected<T: Debug> = T;
}

impl Selector for AlwaysNone {
	type Selected<T: Debug> = ();
}

#[derive(Debug)]
enum Enum<S: Selector> {
    A(StaticOption<S, i32>),
    B(StaticOption<S, u32>),
}

#[derive(Debug)]
enum Meta {
    A(Enum<AlwaysSome>),
    B(Enum<AlwaysNone>),
}

fn main() {
    let x = Meta::A(Enum::B(5));
    let y = Meta::B(Enum::A(()));
    
    println!("{x:?}\n{y:?}");
}

(Playground)


For how I am using this:

The enum Meta in the example above is supposed to represent an explicit uninitialised state of a type. So in the real code, it looks roughly like this:

enum Meta {
    Initialised {
        variant_specific_data: Enum<AlwaysSome>,
        shared_data: SharedDataType,
    },
    Uninitialised {
        variant_specific_uninitialised_data: Enum<AlwaysNone>,
    },
}

This enforces that there are no partially initialised states, which would be allowed when using Option within Enum.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.