Macro-based delegation for enums and structs.
NOTE: This crate is a hard fork and THE successor of the unreleased
enum_delegate
0.3 crate rewrite, which fell to be unmaintained.
use delegation::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),
}
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");
In some cases, a trait or a type requires additional generic parameters to implement delegation. For this case, macro provides for<..>
and where
syntax for #[delegate(derive(..))]
/#[delegate(for(..))]
attribute arguments. Specified generics will be merged with the existing ones, provided by the trait/type definition.
use delegation::delegate;
#[delegate(for(
for<U> Case2<U>
where
U: Named<N> + 'static,
))]
trait Named<N> {
fn name(&self) -> N;
}
struct User(String);
impl Named<String> for User {
fn name(&self) -> String {
self.0.clone()
}
}
#[delegate(derive(
for<N> Named<N>
where
U: Named<N> + 'static,
))]
enum Case1<U> {
User(U),
}
#[delegate]
struct Case2<U>(U);
#[delegate(derive(
Named<String>
where
U: Named<String> + 'static,
))]
enum Case3<U> {
Case1(Case1<U>),
Case2(Case2<U>),
}
let user1 = Case1::User(User("Alice".to_string()));
assert_eq!(user1.name(), "Alice");
let user2 = Case2(User("Bob".to_string()));
assert_eq!(user2.name(), "Bob");
let user3 = Case3::Case1(Case1::User(User("Charlie".to_string())));
assert_eq!(user3.name(), "Charlie");
Because the both sides of the delegation should be marked with the #[delegate]
attribute, it's impossible to make external type delegatable. To handle this, the macro provides the #[delegate(as = my::Def)]
attribute argument for struct fields and enum variants. It uses the provided type as known declaration of some external type. Provided type should be crate-local, and marked with the #[delegate]
macro, and to provide an infallible conversion from external type (including reference-to-reference one).
use delegation::{
private::Either, // non-public, but OK for showcase.
delegate
};
#[delegate]
trait AsStr {
fn as_str(&self) -> &str;
}
impl AsStr for String {
fn as_str(&self) -> &str {
self
}
}
#[delegate(derive(AsStr))]
enum EitherDef {
Left(String),
Right(String),
}
impl<'a> From<&'a mut Either<String, String>> for &'a mut EitherDef {
fn from(t: &'a mut Either<String, String>) -> Self {
#[expect(unsafe_code, reason = "macro expansion")]
unsafe {
&mut *(t as *mut Either<String, String> as *mut EitherDef)
}
}
}
impl<'a> From<&'a Either<String, String>> for &'a EitherDef {
fn from(t: &'a Either<String, String>) -> Self {
#[expect(unsafe_code, reason = "macro expansion")]
unsafe {
&*(t as *const Either<String, String> as *const EitherDef)
}
}
}
impl From<Either<String, String>> for EitherDef {
fn from(t: Either<String, String>) -> Self {
match t {
Either::Left(t) => EitherDef::Left(t),
Either::Right(t) => EitherDef::Right(t),
}
}
}
#[delegate(derive(AsStr))]
struct EitherString(#[delegate(as = EitherDef)] Either<String, String>);
let left = EitherString(Either::Left("left".to_string()));
let right = EitherString(Either::Right("right".to_string()));
assert_eq!(left.as_str(), "left");
assert_eq!(right.as_str(), "right");
Because the both sides of the delegation should be marked with the #[delegate]
attribute, it's impossible to make an external trait delegatable. To handle this, the macro provides the #[delegate(as = my::Def)]
attribute argument for traits. It uses the provided trait as known declaration of some external trait. With this argument, the macro will generate a wrapper type implementing the external trait on it, with the name of the expanded "declaration" trait. By using this wrapper type in #[delegate(derive(ext::Trait as my::TraitDef))]
argument, you can delegate external trait to your type.
use delegation::delegate;
#[delegate(as = AsRef)]
trait AsRefDef<T: ?Sized> {
fn as_ref(&self) -> &T;
}
#[delegate]
trait AsStr {
fn as_str(&self) -> &str;
}
impl AsStr for String {
fn as_str(&self) -> &str {
self
}
}
#[delegate(as = AsStr)]
trait AsStrDef {
fn as_str(&self) -> &str;
}
#[delegate(derive(
AsRef<str> as AsRefDef,
AsStr as AsStrDef,
))]
enum Name {
First(String),
}
let name = Name::First("John".to_string());
assert_eq!(name.as_ref(), "John");
assert_eq!(name.as_str(), "John");
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 a type representing "any of its variants".- "wrapper" type - some type used as a proxy to avoid blanket impls and orphan rules problems.
Implements the Convert
trait for an enum/struct, which allows to convert it to "any of its variants" type.
use delegation::delegate;
#[delegate]
enum Name {
First(FirstName),
Last {
name: LastName,
},
}
generates
// NOTE: 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),
}
}
}
Implements the trait for a "wrapper" type, with inner type implementing the Convert
trait, which "any variant" implements the target trait. I.e. each method in the generated impl
converts self
to the "wrapper" and then to "any of its variants" and invokes the target trait method on it.
Also, it generates a declarative macro used to implement delegation of this trait for a type.
use delegation::delegate;
#[delegate]
trait AsStr {
fn as_str(&self) -> &str;
}
generates
// NOTE: 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 a wrapper type, which inner type implements the `Convert` trait.
// Required to make external types work. In that case the `Wrapper` will be crate-local type.
impl<T> AsStr for Wrapper<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.
}
}
// Definition of macro which implements trait for the provided type.
// It invokes when `for`/`derive` arguments are provided to the `#[delegate]` macro.
macro_rules! AsStr {
($trait_path:path, $ty:ty, $wrapper:ty) => {
impl $trait_path for $ty {
fn as_str(&self) -> &str {
Wrapper(self).convert().as_str()
}
}
};
}
- Both struct/enum and trait should be marked with the
#[delegate]
macro attribute. - Struct or enum variant should contain only a single field.
- Trait methods must have an untyped receiver.
- Supertraits or
Self
trait/method bounds except marker traits likeSized
,Send
orSync
are not supported yet. - Associated types/constants are not supported yet.
- Lifetimes in methods are limited to be early-bounded in some cases (see rust-lang/rust#87803).
Self
type is limited to be used in methods return types.
delegation
was highly inspired by the 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).
Derives a method to return a borrowed pointer to the inner value, cast to a trait object, using the enum_derive::EnumInnerAsTrait
.
Slower though, more similar to dynamic dispatch. Also, less ergonomic due do usage of function-like macros.
delegation
is a hard fork and THE successor of the unreleased enum_delegate
0.3 crate rewrite, which fell to be unmaintained.
This crate is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE] or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT] or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.