Crate enum_ptr

source
Expand description

This crate provides a custom derive macro EnumPtr to generate bridges between an enum T and Compact<T> with minimum cost. Compact<T> is the compact representation of T, and it is only one pointer wide.

This is viable because some types’ low bits are always zeros. Compact<T> utilizes these bits to store the tag (discriminant).

§Examples

use enum_ptr::{Aligned, Compact, EnumPtr, ShiftUsize, Unit};

#[derive(EnumPtr)]
#[repr(C, usize)] // required
enum Foo<'a, T: Aligned> {
    A(T),             // supports any `T: Aligned`
    B(&'a u64),
    C(Unit),          // use `Unit` for unit variants
    D(ShiftUsize<3>), // you can even use non-pointers
    E(Box<i64>),
}

let compact_foo: Compact<_> = Foo::A(&1u64).into();
let original_foo: Foo<_> = compact_foo.into();

§Usage

This crate provides multiple flavors of APIs.

§Flavor 1: copy everywhere

If your enum type is Copy (e.g., consists of only &Ts), you can mark it with #[enum_ptr(copy)]. Each time you need to use it, just copy and extract it. Easy-peasy!

Due to language limitations, we cannot automatically infer copy.

Click to show examples
use enum_ptr::{Compact, EnumPtr};

#[derive(EnumPtr, Debug, Clone, Copy, PartialEq, Eq)]
#[enum_ptr(copy)] // required
#[repr(C, usize)]
enum Foo<'a, 'b> {
    A(&'a i32),
    B(&'b u32),
}

let foo: Compact<_> = Foo::A(&1).into();
assert_eq!(foo.extract(), Foo::A(&1));
assert_ne!(foo.extract(), Foo::B(&2));

§Flavor 2: get_ref & get_mut

If your enum type is not Copy, and you happens to only have references to the compact value, you can use get_ref and get_mut to get references to the object that it points to.

For example, if you hold a compact Box<T>, you can use these APIs to access &T and &mut T. Since there’s no Box<T> in the memory (but only its compact form), we cannot create &Box<T> and &mut Box<T>. The target types are specified by FieldDeref and FieldDerefMut.

Click to show examples
use enum_ptr::{get_mut, get_ref, Compact, EnumPtr};

#[derive(EnumPtr)]
#[repr(C, usize)]
enum Foo {
    A(Box<i32>),
    B(Box<u32>),
}

let mut foo: Compact<_> = Foo::A(Box::new(1)).into();
assert_eq!(get_ref!(foo, Foo::A), Some(&1));
assert_eq!(get_mut!(foo, Foo::A), Some(&mut 1));

§Flavor 3: borrow & borrow_mut

get_ref and get_mut can be troublesome if you want to deal with multiple variants at together. In that case, you can use borrow and borrow_mut. They will return derived reference types that you can match.

Check the documentation of EnumPtr for more details.

Click to show examples
use enum_ptr::{Compact, EnumPtr};

#[derive(EnumPtr, Debug)]
#[enum_ptr(borrow, borrow_mut)] // required
#[repr(C, usize)]
enum Foo {
    A(Box<i32>),
    B(Option<Box<u32>>),
}

// enum FooRef<'enum_ptr> {
//     A(&'enum_ptr i32),
//     B(Option<&'enum_ptr u32>),
// }

// enum FooRefMut<'enum_ptr> {
//     A(&'enum_ptr mut i32),
//     B(Option<&'enum_ptr mut u32>),
// }

let mut foo: Compact<_> = Foo::A(Box::new(1)).into();
match foo.borrow() {
    FooRef::A(inner) => assert_eq!(inner, &1),
    _ => unreachable!(),
}
match foo.borrow_mut() {
    FooRefMut::A(inner) => assert_eq!(inner, &mut 1),
    _ => unreachable!(),
}

§Flavor 4: map_ref & map_mut (legacy)

map_ref and map_mut will copy (= core::mem::transmute_copy) temporary objects out of its compact ones, that core::mem::forget-ed as soon as your closure ends, so that no destructor is needed to be run. They are important for internal implementations, but less useful for lib users.

Click to show examples
use enum_ptr::{Compact, EnumPtr};

#[derive(EnumPtr, Debug, PartialEq, Eq)]
#[repr(C, usize)]
enum Foo {
    A(Box<i32>),
    B(Box<u32>),
}

let mut foo: Compact<_> = Foo::A(Box::new(1)).into();

let result = foo.map_ref(|f| match f {
    Foo::A(r) => **r,
    _ => unreachable!(),
});
assert_eq!(result, 1);

unsafe {
    foo.map_mut(|f| match f {
        Foo::A(r) => **r = 2,
        _ => unreachable!(),
    });
}
assert_eq!(foo.extract(), Foo::A(Box::new(2)));

§Extension

Most of the important traits are public. You can implement them for your own types.

§Limitations

Suppose we are deriving from Foo, then

  • Foo must have a #[repr(C, usize)].
    • According to the RFC and the Rust Reference, #[repr(C, usize)] guarantees the memory layout and discriminant values. Thus, we can safely transmute between two representations.
  • Each variant of Foo must have exactly one field.
    • Unit variants are not allowed due to performance concerns.
    • If you need a unit variant, use Unit.
  • Each variant of Foo must have enough alignment to store the tag.
    • Currently this crate cannot utilize high bits.

Any violation of these rules will trigger a compilation error.

§Features

  • alloc (default)Box, Rc and Arc support

Macros§

Structs§

Traits§

Derive Macros§