[][src]Module tagged_box::manually_impl_enum

A guide to manually implementing a tagged enum

A good start is by looking at the code generated by tagged_box!. Given the following macro invocation:

tagged_box! {
    struct Container, enum Item {
        Integer(i32),
        Boolean(bool),
    }
}

This code will be generated:

#[repr(transparent)]
struct Container {
    value: TaggedBox<Item>,
}

impl TaggableContainer for Container {
    type Inner = Item;

    fn into_inner(self) -> Self::Inner {
        enum EnumCounter {
            Integer,
            Boolean,
        }

        unsafe {
            match self.value.discriminant() {
                discrim if discrim == EnumCounter::Integer as _ =>
                    Item::Integer(TaggedBox::into_inner(self.value)),
                discrim if discrim == EnumCounter::Boolean as _ =>
                    Item::Boolean(TaggedBox::into_inner(self.value)),
                _ => panic!(),
            }
        }
    }
}

enum Item {
    Integer(i32),
    Boolean(bool),
}

impl TaggableInner for Item {
    fn into_tagged_box(self) -> TaggedBox<Self> {
        enum EnumCounter {
            Integer,
            Boolean,
        }

        match self {
            Self::Integer(value) => TaggedBox::new(value, EnumCounter::Integer as _),
            Self::Boolean(value) => TaggedBox::new(value, EnumCounter::Boolean as _),
        }
    }

    fn from_tagged_box(tagged: TaggedBox<Self>) -> Self {
        enum EnumCounter {
            Integer,
            Boolean,
        }

        unsafe {
            match tagged.discriminant() {
                discrim if discrim == EnumCounter::Integer as _ =>
                    Self::Integer(TaggedBox::into_inner(tagged)),
                discrim if discrim == EnumCounter::Boolean as _ =>
                    Self::Boolean(TaggedBox::into_inner(tagged)),
                _ => panic!(),
            }
        }
    }

    unsafe fn ref_from_tagged_box<F>(tagged: &TaggedBox<Self>, callback: F)
    where
        F: FnOnce(&Self),
    {
        enum EnumCounter {
            Integer,
            Boolean,
        }

        unsafe {
            match tagged.discriminant() {
                discrim if discrim == EnumCounter::Integer as _ => {
                    let variant = ManuallyDrop::new(Self::Integer(tagged.as_ptr::<i32>().read()));
                    (callback)(&variant)
                }
                discrim if discrim == EnumCounter::Boolean as _ => {
                    let variant = ManuallyDrop::new(Self::Boolean(tagged.as_ptr::<bool>().read()));
                    (callback)(&variant)
                }
                _ => panic!(),
            }
        }
    }
}

This is a lot of code, so we'll break it down a bit.
The first piece of code generated is this struct:

#[repr(transparent)]
struct Container {
    value: TaggedBox<Item>,
}

This is the 'handle' if you will. It allows you to generically hold multiple different instances of the same overarching enum that have different internal types. It's essentially an enum and a Box rolled into one. However, unlike leaving the safety guarantees up to you, the tagged_box! macro creates a default implementation that makes sure that everything goes smoothly.

Next is the implementation of TaggableContainer for Container, which is the safe interface for retrieving the correct variant of the enum from the backing TaggedBox.

Then we have the Item enum, which is a representation of what the TaggedBox is holding, and can be conveniently gotten from any Container instance. This has the TaggableInner trait implemented on it, which allows us to have convenient things like Clone, PartialEq and Ord.