Match on a bunch of static pointers


#1

I need a type which can have a bunch of variants with no data (C like enums) which I can match on. It is a closed set. Enums would do just fine and were designed for this, but
there are hundreds of variants and lots of methods. Eg:

enum Type {
	Variant0,
	//...
	Variant150,
}

fn get_some_info(&self) -> i32 {
	// match with 151 cases.
}

fn get_some_other_info(&self) -> i32 {
	// match with 151 cases again.
}

Now add like 10 methods and it becomes ugly and a monster file. With the enum approach, I also can’t have variants have their own non polymorphic methods.

I figured I could use trait objects here. Of course I won’t box them as there’s no data. I can use a static lifetime. So I need to store
the variants as static data.
Eg:

trait Type {
	//methods 
}

struct Variant0;
impl Type for Variant0 {/*methods*/}
// ...
struct Variant150;
impl Type for Variant150 {/*methods*/}

static VARIANT0: &Variant0 = &Variant0;
// ...
static VARIANT150: &Variant150 = &Variant150;

Cool. Now I just use &'static Type in the places I could have used enum as Type and pass it the static variables. But then, how do I match?
As far as I’m aware of, static variables are stored in the executable and their memory addresses won’t change. So I guess I could match on raw pointer values.

fn do_something(type: &'static Type) {
	match type as *const Type {
		VARIANT0 as *const Type => // boom error.
	}
}

I tried other ways like:

fn do_something(type: &'static Type) {
	const VARIANT0_addr: usize = VARIANT0 as *const Variant0 as usize;
	match t as *const Trait as usize { // error here..
		VARIANT0_addr => println!("HELLO"),
	}
}

But this gives a more confusing error saying pointer to traits can’t be casted to usize. Why? I thought a pointer is a pointer.

Soo how do I do this? One option is using polymorphism and implement these matches as methods on the trait. I don’t like this because
I am serializing and deserializing these variants as their numeric values. While deserializing, I will need to use a match for matching on the number anyway. So I would like to use match for serialization part as well and keep them in a single place rather than scattering the numeric values everywhere.


#2

But this gives a more confusing error saying pointer to traits can’t be casted to usize. Why? I thought a pointer is a pointer.

A pointer to a trait is a fat pointer. Doing this conversion drops the vtable, which might be unintended. You are, however, allowed to explicitly convert it into a thin pointer first: t as *const Trait as *const u8 as usize.


Here’s how I’d do it.

//--------------------------
// many things seen here can be generated with a macro

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Discriminant {
    VariantA,
    VariantB,
    VariantC,
}

// unit structs can double as both base types for method impls,
// and as static constants, as long as you're not relying on their
// address (which this implementation does not).
pub struct VariantA;
pub struct VariantB;
pub struct VariantC;

// you need this for serialization.
pub trait ToDiscriminant {
    fn to_discriminant(&self) -> Discriminant;
}
impl ToDiscriminant for VariantA {
    fn to_discriminant(&self) -> Discriminant { Discriminant::VariantA }
}
impl ToDiscriminant for VariantB {
    fn to_discriminant(&self) -> Discriminant { Discriminant::VariantB }
}
impl ToDiscriminant for VariantC {
    fn to_discriminant(&self) -> Discriminant { Discriminant::VariantC }
}

impl Discriminant {
    // you need this to deserialize
    pub fn into_object(self) -> &'static Thing {
        match self {
            Discriminant::VariantA => &VariantA,
            Discriminant::VariantB => &VariantB,
            Discriminant::VariantC => &VariantC,
        }
    }
}

//--------------------------
// code that can't be macro-generated

pub trait Thing: ToDiscriminant { /* methods */ }
impl Thing for VariantA { /* methods done in an A-like manner */ }
impl Thing for VariantB { /* methods done in with a side of B */ }
impl Thing for VariantC { /* methods done by the C */ }