#macro-derive #petgraph #typed #type-safe #proc-macro

macro enumcapsulate-macros

Procedural macros for 'enumcapsulate' crate

5 releases (3 breaking)

new 0.4.0 Nov 18, 2024
0.3.0 May 24, 2024
0.2.2 May 23, 2024
0.2.1 May 5, 2024
0.1.0 Jan 22, 2024

#1033 in Procedural macros


Used in enumcapsulate

MPL-2.0 license

75KB
1.5K SLoC

enumcapsulate-macros

Crates.io Crates.io Crates.io docs.rs

Derive macros for enumcapsulate crate.


Macros

The enumcapsulate-macros proc-macro crate exports the following derive macros:

Derive macro Functionality
FromVariant Derives impls for Self: enumcapsulate::FromVariant<T> for each non-unit variant type T.
IntoVariant Derives impls for Self: enumcapsulate::FromVariant<T> for each non-unit variant type T.
From Derives impls for Self: core::convert::From<T> for each non-unit variant type T.
TryInto Derives impls for T: core::convert::TryFrom<Self> for each non-unit variant type T.
AsVariant Derives impls for Self: enumcapsulate::AsVariant<T> for each non-unit variant type T.
AsVariantMut Derives impls for Self: enumcapsulate::AsVariantMut<T> for each non-unit variant type T.
AsVariantRef Derives impls for Self: enumcapsulate::AsVariantRef<T> for each non-unit variant type T.
VariantDiscriminant Derives impl for Self: enumcapsulate::VariantDiscriminant.

… as well as an umbrella derive macro Encapsulate.

[!NOTE] The implementations generated by the From and FromVariant, as well as the TryInto and IntoVariant derive macro pairs are semantically equivalent.

#[derive(Encapsulate)]

The umbrella derive macro Encapsulate allows for conveniently deriving AsVariant, AsVariantMut, AsVariantRef, From, FromVariant, IntoVariant, TryInto, VariantDiscriminant, and VariantDowncast all at once.

The following two snippets are semantically equivalent:

#[derive(Encapsulate)]
enum Enum { /* ... */ }
#[derive(
    FromVariant,
    IntoVariant,
    From,
    TryInto,
    AsVariant,
    AsVariantMut,
    AsVariantRef,
    VariantDiscriminant,
)]
enum Enum { /* ... */ }

[!TIP] If the list of trait derives produced by #[derive(Encapsulate)] is too broad of a stroke your your particular use case then your can selectively opt them out by use of an #[enumcapsulate(exclude())] attribute on the enum itself.

You can even opt trait derives back in at the variant-level, by use of #[enumcapsulate(include)] for previously opted-out derives, or individually by use of #[enumcapsulate(include())]:

#[derive(Encapsulate)]
#[enumcapsulate(exclude(From, TryInto))]
enum Enum {
    // Excluded from `From` and `TryInto` derives
    // due to existing enum-level attribute:
    VariantA(VariantA),

    // Selectively re-included for all derives:
    #[enumcapsulate(include)]
    VariantB(VariantB),

    // Selectively re-included for `From` derive:
    #[enumcapsulate(include(From))]
    VariantC(VariantC),
}

#[derive(AsVariant)]

The AsVariant derive macro requires the variant's field type to implement Clone.

Macro helper attributes

Most of the derive macros support helper attributes:

Enum attributes

#[enumcapsulate(exclude())]

Exclude this variant from trait derivation.

  • #[enumcapsulate(exclude())]

    Exclude variant from specific enumcapsulate derive macros.

If you wish to opt out of a select few of Encapsulate's trait derives, then you can do so by use of an #[enumcapsulate(exclude())] attribute:

#[derive(Encapsulate)]
#[enumcapsulate(exclude(From, TryInto))]
enum Enum { /* ... */ }

[!TIP] If you wish to opt all but a select few variants out of a trait's derive, then you can do so by use of an #[enumcapsulate(exclude())] attribute on the enum, together with a #[enumcapsulate(include())] attribute on the variant:

#[derive(Encapsulate)]
#[enumcapsulate(exclude(From, TryInto))]
enum Enum {
    // Excluded from `From` and `TryInto` derives
    // due to existing enum-level attribute:
    VariantA(VariantA),

    // Selectively re-included for all derives:
    #[enumcapsulate(include)]
    VariantB(VariantB),

    // Selectively re-included for
    // just the `From` derive:
    #[enumcapsulate(include(From))]
    VariantC(VariantC),

    // ...
}

Variant attributes

[!NOTE] Variant-level attributes have a higher precedence than their equivalent enum-level attributes and thus act as a selective override if both are present.

#[enumcapsulate(exclude())]

Exclude this variant from trait derivation.

  • #[enumcapsulate(exclude)]

    Exclude variant from all enumcapsulate derive macros.

  • #[enumcapsulate(exclude())]

    Exclude variant from specific enumcapsulate derive macros.

#[derive(Encapsulate)]
enum Enum {
    // Included by default.
    VariantA(VariantA),
    
    // Excluded from all derives:
    #[enumcapsulate(exclude)]
    VariantB(VariantB),
    
    // Excluded from just the `From` and `TryInto` derives:
    #[enumcapsulate(exclude(From, TryInto))]
    VariantC(VariantC),
}

[!TIP] Combine the use of #[enumcapsulate(exclude)] with #[enumcapsulate(include()] in order to exclude a variant from all but a select few derive macros.

// Exclude variant from all derives,
// then selectively re-include it for
// just the `From` and `TryInto` derives:
#[enumcapsulate(exclude)]
#[enumcapsulate(include(From, TryInto))]

This attribute is recognized by the following variant-based derive macros:

  • AsVariant
  • AsVariantMut
  • AsVariantRef
  • FromVariant
  • IntoVariant
  • From
  • TryInto

… as well as the umbrella derive macro:

  • Encapsulate

#[enumcapsulate(include()]

Include this variant for specific trait derivation (overriding existing uses of #[enumcapsulate(exclude)]).

  • #[enumcapsulate(include)]

    Include variant from all enumcapsulate derive macros.

  • #[enumcapsulate(include())]

    Include variant from specific enumcapsulate derive macros.

#[derive(Encapsulate)]
#[enumcapsulate(exclude(From, TryInto))]
enum Enum {
    // Included by default.
    VariantA(VariantA),
    
    // Selectively included for just
    // the `From` and `TryInto` derives:
    #[enumcapsulate(exclude)]
    #[enumcapsulate(include(From, TryInto))]
    VariantB(VariantB),
}

[!NOTE] The #[enumcapsulate(include()] variant has a higher precedence than #[enumcapsulate(exclude)], and thus acts as a selective override if both are present on a variant:

// Exclude variant from all derives,
// then selectively re-include it for
// just the `From` and `TryInto` derives:
#[enumcapsulate(exclude)]
#[enumcapsulate(include(From, TryInto))]

This attribute is recognized by the following variant-based derive macros:

  • AsVariant
  • AsVariantMut
  • AsVariantRef
  • FromVariant
  • IntoVariant
  • From
  • TryInto

… as well as the umbrella derive macro:

  • Encapsulate

#[enumcapsulate(field(=)]

Select a specific variant field as target for trait derivation.

  • #[enumcapsulate(field(index = <index>))]

    Select field at index <index> to be used as target.

  • #[enumcapsulate(field(name = "<name>"))]

    Select field with name <name> to be used as target.

#[derive(Encapsulate)]
enum Enum {
    // Select field `1`, i.e. `VariantA`:
    #[enumcapsulate(field(index = 1))]
    VariantA(i32, VariantA),
    
    // Select field `b`, i.e. `VariantB`:
    #[enumcapsulate(field(name = "b"))]
    VariantB { b: VariantB, c: u32 },
}

[!NOTE] When deriving traits for variants with multiple fields a field has to be explicitly specified using #[enumcapsulate(field(=))].

Alternatively the variant can be excluded via #[enumcapsulate(exclude)].

This attribute is recognized by the following variant-based derive macros:

  • AsVariant
  • AsVariantMut
  • AsVariantRef
  • FromVariant
  • IntoVariant
  • From
  • TryInto

… as well as the umbrella derive macro:

  • Encapsulate

Generics

There is limited support for generic enums:

Variants using generic const/type parameters are always excluded when deriving generic traits with enumcapsulate's derive macros.

The reason for this behavior is that implementing generic traits for variants that use any of the generic parameters of the enum tends to result in conflicting implementations in Rust, as shown by the following example program:

use enumcapsulate::FromVariant;

pub struct VariantA;
pub struct VariantB;

#[derive(FromVariant)]
// error[E0119]: conflicting implementations of trait `FromVariant<VariantB>` for type `Enum<VariantB>`
pub enum Enum<T> {
    Unit,
    Generic(T),
    NonGeneric(VariantB),
}

fn main() {
    let _: Enum<VariantA> = Enum::from_variant(VariantA);
    let _: Enum<VariantA> = Enum::from_variant(VariantB);
}

The expanded version of the above makes it easier to see why: The compiler can't prove that T and VariantB are disjoint types.

pub struct VariantA;
pub struct VariantB;

pub enum Enum<T> {
    Unit,
    Generic(T),
    NonGeneric(VariantB),
}

impl<T> FromVariant<T> for Enum<T> { // <- first implementation here
    fn from_variant(variant: T) -> Self {
        Self::Generic(variant)
    }
}

// error[E0119]: conflicting implementations of trait `FromVariant<VariantB>` for type `Enum<VariantB>`
impl<T> FromVariant<VariantB> for Enum<T> { // <- conflicting implementation for `Enum<VariantB>`
    fn from_variant(variant: VariantB) -> Self {
        Self::NonGeneric(variant)
    }
}

fn main() {
    let _: Enum<VariantA> = Enum::from_variant(VariantA);
    let _: Enum<VariantA> = Enum::from_variant(VariantB);
}

So in order to avoid such pitfalls altogether enumcapsulate's derive macros will skip impl<T> FromVariant<T> for Enum<T>, since it uses a generic type (or const) parameter of Enum<T>.

So all you have to do is provide your own non-generic implementations for specific type instances of your generic type yourself, filling any gaps left behind by the derive macro:

use enumcapsulate::FromVariant;

pub struct VariantA;
pub struct VariantB;

#[derive(FromVariant)]
pub enum Enum<T> {
    Unit,
    Generic(T),
    NonGeneric(VariantB),
}

// Notice how the trait is implemented on
// a specific type of the `Enum<T>` kind,
// rather than on the generic kind itself:
impl From<VariantA> for Enum<VariantA> {
    fn from(value: VariantA) -> Self {
        Self::Generic(value)
    }
}

fn main() {
    let _: Enum<VariantA> = Enum::from_variant(VariantA);
    let _: Enum<VariantA> = Enum::from_variant(VariantB);
}

Documentation

Please refer to the documentation on docs.rs.

Contributing

Please read CONTRIBUTING.md for details on our code of conduct,
and the process for submitting pull requests to us.

Versioning

We use SemVer for versioning. For the versions available, see the tags on this repository.

License

This project is licensed under the MPL-2.0 – see the LICENSE.md file for details.

Dependencies

~240–690KB
~16K SLoC