Derive Macro sort_by_derive::EnumAccessor

source ·
#[derive(EnumAccessor)]
{
    // Attributes available to this derive:
    #[accessor]
}
Expand description

This derive macro is similar to enum_dispatch. enum_dispatch requires structs to implement a common trait, which can be useful if a common set of functions applies to all variants. EnumAccessor takes the opposite approach: common fields and methods are declared at enum level, and you can have variants that don’t have a given field or method. This may be more practical if there is a large amount of variants and your only concern is accessing fields, because individual structs just hold data. This is typical for events - they represent a state change and are generally consumed as a whole, individual structs have no code of their own.

Field accessor

After adding derive(EnumAccessor) to the enum, fields are declared as accessor(field: type) attributes:

This will derive the accessor methods fn name(&self) -> &type; andfn name_mut(&mut self) -> &mut type;, and return a reference to the field of the same name on any variant.

use sort_by_derive::EnumAccessor;

#[derive(EnumAccessor)]
#[accessor(a: u16)]
#[accessor(b: u16)]
enum E {
    Variant1{a: u16, b: u16},
    Variant2{a: u16, b: u16, c: u32},
}

let v1 = E::Variant1{a: 1, b: 1};
let mut v2 = E::Variant2{a: 1, b: 1, c: 2};

// Accessor methods are generated for the specified members
assert_eq!(*v1.a(), 1);
assert_eq!(*v2.b(), 1);

// Mutable accessors are also generated
*v2.a_mut() = 2;
assert_eq!(*v2.a(), 2);

So you can take any E, all variants will have a, a_mut, b, b_mut

#[derive(EnumAccessor)]
#[accessor(a: u16)]
#[accessor(b: u16)]
enum E {
    Variant1{a: u16, b: u16},
    Variant2{a: u16, b: u16, c: u32},
}

fn do_something(e: &mut E) -> u16 {
    let field_value = *e.a(); // take the value of that field, whatever variant it is
    *e.a_mut() = 42; // use the accessor method returning a &mut to the field
    field_value
}

let mut v1 = E::Variant1{a: 11, b: 0};
assert_eq!(do_something(&mut v1), 11);
assert_eq!(*v1.a(), 42);

let mut v2 = E::Variant2{a: 11, b: 0, c: 32};
assert_eq!(do_something(&mut v2), 11);
assert_eq!(*v2.a(), 42);

Use except or only if not all variants have a given field:

#[derive(EnumAccessor)]
#[accessor(a: u16, only(Variant1, Variant2))]
#[accessor(c: u32, except(Variant1, Variant3))]
enum E {
    Variant1 { a: u16, b: u16 },
    Variant2 { a: u16, b: u16, c: u32 },
    Variant3 { d: u64 },
    Variant4 { c: u32 },
}

assert_eq!(E::Variant1 { a: 1, b: 2 }.a(), Some(&1));
assert_eq!(E::Variant2 { a: 1, b: 2, c: 0 }.a(), Some(&1));
assert_eq!(E::Variant3 { d: 0 }.a(), None);
assert_eq!(E::Variant2 { a: 0, b: 0, c: 2 }.c(), Some(&2));
assert_eq!(E::Variant1 { a: 0, b: 0 }.c(), None);

This derives the same accessor methods, but the return type will be Option<&type> and Option<&mut type>. The provided comma-separated list of variants in except will return None. The provided comma-separated list of variants in only must have the given field and will return Some.

Methods without arguments ( i.e. only &self are also supported ). It takes the form: #[accessor(method_name(): type)]. If type is a &mut, the generated method will take &mut self instead of &self. This can be useful for accessing mutable derived methods of nested enums.

To avoid name clashes, accessors can be given an alias by using as:

#[derive(EnumAccessor)]
#[accessor(a as a_attr: u16, except(Variant3))]
enum E {
    Variant1 { a: u16 },
    Variant2 { a: u16, c: u32 },
    Variant3 { b: u32 },
}

impl E {
    fn a(&self) -> bool {
        // Unrelated work
    }
}

assert_eq!(E::Variant1 { a: 1 }.a(), true);
assert_eq!(E::Variant1 { a: 1 }.a_attr(), Some(&1));
assert_eq!(E::Variant2 { a: 1, c: 0 }.a_attr(), Some(&1));
assert_eq!(E::Variant3 { b: 0 }.a_attr(), None);
Example

Say we have a series of midi events, they are very similar but with slight variations - they always have some timing information but they may not always have a pitch or channel.

Using #[accessor(global_time: usize)], a global_time(&self) method is derived, along with a global_time_mut(&mut self), so without any boilerplate you can access the timing.

By declaring #[accessor(channel: u8, except(CC))], channel(&self) and channel_mut(&mut self) are derived, but they return Some for NoteOn and NoteOff, and None for CC and Unsupported.

#[derive(EnumAccessor)]
#[accessor(global_time: usize)]
#[accessor(channel: u8, except(CC))]
#[accessor(pitch: u8, except(CC, Unsupported))]
enum Note {
    NoteOn {
        global_time: usize,
        pitch: u8,
        channel: u8
    },
    NoteOff {
        global_time: usize,
        pitch: u8,
        channel: u8
    },
    CC {
        global_time: usize
    },
    Unsupported {
        global_time: usize,
        raw_data: Vec<u8>,
        channel: u8
    }
}

assert!(
    [
        *Note::NoteOn {
            global_time: 42,
            // ...
        }.global_time(),
        *Note::NoteOff {
            global_time: 42,
            // ...
        }.global_time(),
        *Note::CC {
            global_time: 42,
        }.global_time(),
        *Note::Unsupported {
            global_time: 42,
            // ...
        }.global_time()
    ].into_iter().all(|t| t == 42)
);

assert_eq!(
    Note::NoteOn {
        // ...
        pitch: 2,
        // ...
    }.pitch(),
    Some(&2)
);

assert_eq!(
    Note::CC {
        global_time: 42,
    }.pitch(),
    None
);
Method accessor

The general form is #[accessor(method():type)].

As for field access, declaring an exception will make the actual return type an Option<type>.

Named fields is supported, it will consider that the named field is of type Fn() -> type, and call it.

An intricate example:

struct A {
    f1: u8,
    f2: u8
}

impl A {
    fn sum(&self) -> u8 {
        // ...
    }
    fn set(&mut self) -> &mut u8 {
        // ...
    }
}

struct B {
    values: Vec<u8>
}

impl B {
    fn sum(&self) -> u8 {
        // ...
    }
}

#[derive(EnumAccessor)]
#[accessor(sum():u8)]
#[accessor(set(): &mut u8, except(B,C))]
enum E<Get: Fn() -> u8> {
    A(A),
    B(B),
    C{sum: Get}
}

let factor = Arc::new(AtomicU8::new(1));

let [mut a, b, c] = [
E::A(A { f1: 10, f2: 22 }),
E::B(B { values: vec![9, 4, 3, 2] }),
{
let factor = factor.clone();
E::C {
sum: move || 21 * factor.load(Ordering::Relaxed),
}
}];

assert_eq!(32, a.sum()); // sum() is available without matching against E::A, E::B or E::C
if let Some(value) = a.set() { // set() is only available for E::A and returns a &mut u8, so we get a Option<&mut u8>
*value = 0;
}
assert_eq!(22, a.sum());
assert_eq!(18, b.sum());
assert_eq!(21, c.sum());
factor.store(2, Ordering::Relaxed);
assert_eq!(42, c.sum());

Limitation

  • On unnamed variants, EnumAccessor only considers the first parameter.