1 unstable release

0.3.0 Feb 17, 2023

#6 in #delegate

Download history 4/week @ 2024-02-19 15/week @ 2024-02-26 9/week @ 2024-03-04 6/week @ 2024-03-11 7/week @ 2024-03-18 6/week @ 2024-03-25 31/week @ 2024-04-01 114/week @ 2024-04-08 6/week @ 2024-04-22

152 downloads per month
Used in 2 crates (via temporary_enum_delegate_0…)

MIT/Apache

57KB
1.5K SLoC

NOTE

This is an unpublished version (with bugs) forked from enum_delegate (revision f99967517e925f9630735836fb1601747df9c71d). The original authors are Reinis Mazeiks, 50U10FCA7. This crate is to resolve dependency conflicts in another crate concurrent_tor. Since that crate depends on enum_delegate ^= "0.3.0", this is a temporary upload of enum_delegate.

enum_delegate

gitlab crates.io docs.rs

Provides trait delegation functionality for enums and structs.

Note: Name contains enum_* part due to historical reasons. It is not limited to enums.

[dependencies]
enum_delegate = "0.4"

Example

use enum_delegate::delegate;

#[delegate(for(LastName))]
trait AsStr {
    fn as_str(&self) -> &str;
}

impl AsStr for String {
    fn as_str(&self) -> &str {
        self
    }
}

#[delegate(derive(AsStr))]
struct FirstName(String);

#[delegate]
struct LastName {
    name: String,
}

#[delegate(derive(AsStr))]
enum Name {
    First(FirstName),
    Last(LastName),
}

fn main() {
    let name = Name::First(FirstName("John".to_string()));
    assert_eq!(name.as_str(), "John");

    let name = Name::Last(LastName {
        name: "Doe".to_string(),
    });
    assert_eq!(name.as_str(), "Doe");
}

How it works

Crate provides several definitions:

  • delegate macro - derives trait on a new-type struct or enum, invoking it on its inner type.
  • Convert trait - converts enum or struct to type represents "any of its variant".

#[delegate] expansion on type

Implements Convert trait to enum/struct, which allows to convert it to "any of its variant" type.

Source

#[delegate]
enum Name {
    First(FirstName),
    Last {
        name: LastName,
    },
}

Generated

Note: Example is simplified for readability.

impl Convert for Name {
    type Output = Either<FirstName, LastName>;

    fn convert(self) -> Self::Output {
        match self {
            Name::First(first_name) => Either::Left(first_name),
            Name::Last { name } => Either::Right(name),
        }
    }
}

#[delegate] expansion on trait

Implements the trait for any type that implements Convert trait, which "any variant" implements target trait. I.e. each method in generated impl convert self to "any of its variant" and invokes target trait method on it.

Source

#[delegate]
trait AsStr {
    fn as_str(&self) -> &str;
}

Generated

Note: Example is simplified for readability.

// Implementation for "any variant of enum or struct" type.
impl<L: AsStr, R: AsStr> AsStr for Either<L, R> {
    fn as_str(&self) -> &str {
        match self {
            Either::Left(left) => left.as_str(),
            Either::Right(right) => right.as_str(),
        }
    }
}

// Implementation for any type that implements `Convert` trait.
impl<T> AsStr for T
where
    T: Convert,
    T::Output: AsStr,
{
    fn as_str(&self) -> &str {
        let this = self.convert(); // convert type to "any of its variant".
        this.as_str() // call the method.
    }
}

Limitations

  • Both struct/enum and trait should be marked with #[delegate] macro attribute.
  • Struct or enum variant should contain only single field.
  • Trait methods must have an untyped receiver.
  • Implementation for external (remote) types or traits is not supported yet.
  • Supertraits or Self trait/method bounds except marker traits like Sized, Send or Sync are not supported yet.
  • Associated types/constants are not supported yet.
  • #[delegate(for(Enum<Generics>))] supports only concrete generic types.

Alternatives

Dynamic dispatch

Rust mechanism for dynamic dispatch using trait objects, which adds runtime overhead.

Example

trait AsStr {
    fn as_str(&self) -> &str;
}

impl AsStr for String {
    fn as_str(&self) -> &str {
        self
    }
}

struct FirstName(String);

impl AsStr for FirstName {
    fn as_str(&self) -> &str {
        &self.0
    }
}

fn do_something_with_string(s: &dyn AsStr) {
    println!("{}", s.as_str());
}

fn main() {
    let name = "John".to_string();
    do_something_with_string(&name);

    let name = FirstName(name);
    do_something_with_string(&name);
}

enum_dispatch

enum_delegate was highly inspired by enum_dispatch crate. It provides similar functionality, but has more limitations:

  • Supports only enums.
  • Using enum_dispatch between crates is impossible due to limitations of its design.
  • Order-dependent macro expansion (in some cases your code fails if items marked with the macro has different order than macro expects).

enum_derive

Derive a method to return a borrowed pointer to the inner value, cast to a trait object, using enum_derive::EnumInnerAsTrait. Slower though, more similar to Dynamic dispatch.


lib.rs:

Code generation of #[delegate] macro.

Dependencies

~4MB
~82K SLoC