1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// TODO: add examples

/// This trait represents the behaviour of an `enum`/tagged union.
/// **Note**: it should only be implemented with `#[derive(Phenotype)]`
pub trait Phenotype {
    /// The number of variants of the enum.
    const NUM_VARIANTS: usize;

    /// The number of bits needed to represent every variant of the enum.
    /// For example, if the enum has 4 variants, then two bits are needed.
    const BITS: usize;

    /// The number of bits `Phenotype` uses to represent and instance of a type.
    /// If the type `Phenotype` is being implemented for is generic,
    /// this will be `None`, as sizes may vary accross different
    /// generic parameters. For example, `Type<usize>` could be differently
    /// sized than `Type<[usize; 4]>`
    const PEAPOD_SIZE: Option<usize>;

    /// Whether using `Phenotype` produces a more compact representation.
    /// Will be `Some(true)` if implementations are the same size.
    /// If the type `Phenotype` is being implemented for is generic,
    /// this will be `None`, as sizes may vary accross different
    /// generic parameters. For example, `Type<usize>` could be differently
    /// sized than `Type<[usize; 4]>`
    const IS_MORE_COMPACT: Option<bool>;

    /// A type that represents all the data an enum can contain.
    /// This should be a union whose fields each represent a particular
    /// enum variant.
    type Value;

    /// Takes an enum variant and `cleave`s it into a two parts:
    /// a tag, and an union representing the data the enum can hold.
    /// If the enum variant doesn't hold data, `None` is returned as
    /// the second tuple element.
    fn cleave(self) -> (usize, Self::Value);

    /// Takes a tag and a value and recombines them into a proper
    /// instance of an enum variant. Calling this function with incorrect
    /// inputs can result in undefined behavior. The tag must always match
    /// the state that the union is in.
    ///
    /// For example, consider the following example
    /// ```
    /// #[derive(Phenotype)]
    /// enum UB {
    ///     U(usize), // -> tag = 0
    ///     B(bool)   // -> tag = 1
    /// }
    ///
    /// // This is the type <UB as Phenotype>::Value
    /// union Value {
    ///     U: usize,
    ///     B: bool
    /// }
    ///
    /// use peapod::Phenotype;
    /// fn main {
    ///     let ub = UB::U(3);
    ///     let (_, data) = ub.cleave();
    ///     // ** DANGER **
    ///     // We are interpreting 3 as a bool! That's undefined behavior.
    ///     let BAD = <UB as Phenotype>::reknit(1, data);
    /// }
    /// ```
    fn reknit(tag: usize, value: Self::Value) -> Self;
}

/// Some helpful methods for using `Phenotype`
pub trait PhenotypeDebug: Phenotype {
    /// Returns the tag that Phenotype uses internally
    /// to identify the enum variant.
    /// **Note**: this is different from the tag the compiler
    /// uses or a discriminant that was manually specified. It
    /// only has meaning in the context of `Phenotype`.
    fn discriminant(&self) -> usize;

    /// Takes a tag and returns a string that represents
    /// the variant. For example, it might return something
    /// like `Result::Ok` for 0 and `Result::Err` for 1 if `Phenotype`
    /// was derived on the `Result` type.
    fn debug_tag(tag: usize) -> &'static str;
}