Ambassador - Delegate trait implementations via procedural macros
Delegating the implementation of traits to enum variants or fields of a struct normally requires a lot of boilerplate code. Ambassador is an attempt to eliminate that boilerplate by deriving the delegating trait implementation via procedural macros.
The minimum supported Rust version is 1.53.0.
Installation
cargo add ambassador
General Usage
#[delegatable_trait]
First we need to make our trait available for delegation by adding a #[delegatable_trait]
attribute to it (this also makes your trait delegatable in other crates):
use delegatable_trait;
// <-------
#[derive(Delegate)]
& #[delegate(Trait)]
Now we can delegate the implementation of our trait to a struct field/enum variants by adding #[derive(Delegate)]
and its associated attribute #[delegate(Trait)]
to it:
use Delegate;
;
// <-------
// <-------- Delegate implementation of Shout to struct field
;
#[delegate(..., target = "foo")]
- target
key
For structs with multiple fields, the field that should act as delegation target can be specified via the target
key:
// <-------- Delegate implementation of Shout to struct field .foo
This also works for tuple structs with multiple fields, by using their index as a target key:
// <-------- Delegate implementation of Shout to second field of type Dog
;
#[delegate(..., target = "self")]
- target="self"
Types that implement all the methods of a trait without implementing the trait itself,
can be made to implement that trait by setting target="self"
.
This doesn't work for traits with associated types and constants, and requires the where clause to be added explicitly (see where
key).
If the type doesn't actually implement the methods (possibly due to an incomplete where
clause) this can cause a [unconditional_recursion]
error.
A possible use case of this is when refactoring some methods of a public type into a trait, the type still needs to implement the methods outside the trait for semver reasons, and using this feature reduces the boilderplate of implementing the trait with the same methods.
;
#[delegate(..., where = "A: Shout")]
- where
key
To make a delegation apply only for certain generic bounds, similar to a native where clause, you can specify a where
attribute:
A where clause is automatically applied that makes sure the target field implements the trait being delegated
// <---- Delegate implementation of Shout to .foo field if foo field implements Debug
#[delegate(Shout<X>)]
- trait generics
We can also delegate traits with generics.
When doing this all instances of X
and 'x
followed by arbitrary digits eg. X0
X12
'x3
are treated as maximally generic.
The automatically added where clause ensures they are valid for the inner type being delegated to.
Explict where clauses to further refine these types can be added as normal.
Specific types can be used instead of X
to only derive for those.
use ;
use Display;
// <-------
;
// <-------- X is fully generic
// The automatic where clause ensures X: Display
// We could also use #[delegate(Shout<& 'a str>, generics = "'a")] to only delegate for &str
;
For remote traits: #[delegatable_trait_remote]
If you want to make an existing trait that lives outside you crate available for delegation, you can do so by copy-pasting it's signature into your code and using the #[delegatable_trait_remote]
attribute (see full code sample):
use delegatable_trait_remote;
use Display;
// <-------- Delegate implementation of Display to struct field
;
For remote types #[delegate_remote]
If you want to make an existing type that lives outside you crate delegate, you can do so by copy-pasting it's definition into your code and using the #[delegate_remote]
attribute (see full code sample):
If the type is a struct, not all the fields have to be public, only the ones being delegated to.
use *;
Note: Because of the orphan rule #[delegatable_trait_remote]
and #[delegate_remote]
can't be combined
Usage Examples
In this example we have a trait Shout
that is implemented for both Cat
and Dog
.
Delegate to enum variants
We now add an Animal
enum and add a delegated trait implementation for Shout
,
that calls the respective implementations of the enum variants:
use ;
;
;
Delegate to tuple struct field
Delegating a trait implementation for a tuple struct (only single-field tuples supported for now), for e.g. a newtype pattern.
use ;
;
;
Delegate to struct field
Delegating a trait implementation for a normal struct
use ;
;