1 stable release
1.0.0 | Nov 10, 2021 |
---|
#1449 in Rust patterns
20KB
451 lines
tagged-pointer-as-enum
A set of structs, traits and macros to implement tagged pointers.
Basic usage
// import macro
use tagged_pointer_as_enum::tagged_enum;
// declare it like a normal enum but inside tagged_enum! macro
tagged_enum! {
enum E {
// specify how many bits you want to be used by tag.
// should be at least log2(count(variants) + 1), but you can set more
// if you need to keep ABI stable after adding new variants
bits = 2;
A(u8),
B(Box<String>),
C(i16),
}
}
// macro generates constructors:
let a = E::A(42_u8);
let b = E::B(Box::new(String::from("foo")));
let c = E::C(300_i16);
// and a helper module with tags
assert_eq!(tags::A, 1);
assert_eq!(tags::B, 2);
assert_eq!(tags::C, 3);
// these tags can be used to check variant of enum
assert_eq!(a.tag(), tags::A);
assert_eq!(b.tag(), tags::B);
assert_eq!(c.tag(), tags::C);
// only variants that behave like containers can be borrowed
assert_eq!(b.borrow_value::<Box<String>, String>(), &String::from("foo"));
// borrowing values variants is impossible
// because there's no memory location containing value WITHOUT tag
// of course, you can get values back
assert_eq!(a.unwrap::<u8>(), 42);
assert_eq!(b.unwrap::<Box<String>>(), Box::new(String::from("foo")));
assert_eq!(c.unwrap::<u16>(), 300);
Custom variant types
By default the following types can be used as variants:
u8
u16
u32
i8
i16
i32
Box<T>
Option<Box<T>>
It is possible to use other types by implementing TaggedPointerValue
for them:
use tagged_pointer_as_enum::{tagged_enum, TaggedPointerValue};
struct Custom {
low: u8,
high: u8
}
// even if it looks like a marker trait in fact it's not
impl TaggedPointerValue for Custom {}
tagged_enum! {
enum E {
bits = 1;
Custom(Custom),
}
}
let custom = E::Custom(Custom { low: 1, high: 2 });
let unwrapped = custom.unwrap::<Custom>();
assert_eq!(unwrapped.low, 1);
assert_eq!(unwrapped.high, 2);
Implementing default traits
Out of the box tagged_enum!
macro generates a struct that doesn't implement any builtin traits.
It is possible to attach #[derive(...)]
metadata similar to how it's usually attached to native Rust enums, however all variant types must also implement these traits.
Only the following traits are supported
Debug
Clone
PartialEq
Eq
Also, Drop
is auto-implemented for all tagged enums.
use tagged_pointer_as_enum::{tagged_enum, TaggedPointerValue};
#[derive(Debug)]
struct NumAbsCompare {
n: i32
}
impl TaggedPointerValue for NumAbsCompare {}
// implement comparison by absolute value
impl PartialEq for NumAbsCompare {
fn eq(&self, other: &Self) -> bool {
self.n.abs() == other.n.abs()
}
}
tagged_enum! {
#[derive(Debug, PartialEq)]
enum E {
bits = 2;
I32(i32),
NumAbsCompare(NumAbsCompare),
}
}
// variants ARE equal if they have the same tag and value
assert_eq!(
E::NumAbsCompare(NumAbsCompare { n: 100 }),
E::NumAbsCompare(NumAbsCompare { n: -100 }),
);
// variants ARE NOT equal if tags are different
assert_ne!(
E::NumAbsCompare(NumAbsCompare { n: 100 }),
E::I32(100),
);
// variants ARE NOT equal if values are different
assert_ne!(
E::NumAbsCompare(NumAbsCompare { n: 100 }),
E::NumAbsCompare(NumAbsCompare { n: 101 }),
);
Dependencies
~0–10MB
~93K SLoC