Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 34 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

<br/>

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.
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.68.0.**

Expand All @@ -29,7 +31,8 @@ More examples are also available https://github.com/hobofan/ambassador/tree/mast

### `#[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):
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):

```rust
use ambassador::delegatable_trait;
Expand All @@ -42,7 +45,8 @@ pub trait Shout {

### `#[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:
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:

```rust
use ambassador::Delegate;
Expand All @@ -68,8 +72,8 @@ For structs with multiple fields, the field that should act as delegation target
#[derive(Delegate)]
#[delegate(Shout, target = "foo")] // <-------- Delegate implementation of Shout to struct field .foo
pub struct WrappedCats {
foo: Cat,
bar: Cat,
foo: Cat,
bar: Cat,
}
```

Expand All @@ -85,16 +89,18 @@ pub struct WrappedAnimals(Cat, Dog);

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.
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.

```rust
#[derive(Delegate)]
#[delegate(Shout, target="self")]
#[delegate(Shout, target = "self")]
pub struct Cat;

impl Cat {
Expand All @@ -106,22 +112,26 @@ impl Cat {

#### `#[delegate(..., where = "A: Shout")]` - `where` key

To make a delegation apply only for certain generic bounds, similar to a [native where clause](https://doc.rust-lang.org/stable/rust-by-example/generics/where.html), you can specify a `where` attribute:
To make a delegation apply only for certain generic bounds, similar to
a [native where clause](https://doc.rust-lang.org/stable/rust-by-example/generics/where.html), you can specify a `where`
attribute:

A where clause is automatically applied that makes sure the target field implements the trait being delegated

```rust
#[derive(Delegate)]
#[delegate(Shout, where = "A: Debug")] // <---- Delegate implementation of Shout to .foo field if foo field implements Debug
#[delegate(Shout, where = "A: Debug"
)] // <---- Delegate implementation of Shout to .foo field if foo field implements Debug
pub struct WrappedFoo<A> {
foo: A,
foo: A,
}
```

#### `#[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.
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.
Expand Down Expand Up @@ -152,7 +162,9 @@ pub struct WrappedCat(Cat);

### 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](./ambassador/tests/run-pass/delegate_trait_remote_display.rs)):
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](./ambassador/tests/run-pass/delegate_trait_remote_display.rs)):

```rust
use ambassador::delegatable_trait_remote;
Expand All @@ -170,7 +182,9 @@ pub struct WrappedCat(Cat);

### 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](./ambassador/tests/run-pass/delegate_remote.rs)):
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](./ambassador/tests/run-pass/delegate_remote.rs)):

If the type is a struct, not all the fields have to be public, only the ones being delegated to.

Expand Down Expand Up @@ -200,13 +214,13 @@ Note: Because of the orphan rule `#[delegatable_trait_remote]` and `#[delegate_r
## Controlling inlining

Both `#[delegatable_trait]` and `#[delegate]` can set the inlining mode for the
delegated methods. You have to use the `inline` key with the values `yes` (i.e.,
`#[inline]`), `no` (no inlining attribute), `always` (i.e.,
`#[inline(always)]`), or `never` (i.e., `#[inline(never)]`).
delegated methods. You have to use the `inline` key with the values `yes` (for
`#[inline]`), `no` (no inlining attribute), `always` (for
`#[inline(always)]`), or `never` (for `#[inline(never)]`).

The value set in `#[delegatable_trait]` is used as the default for all
delegations of that trait, but can be overridden by setting the `inline` key in
`#[delegate]` to a different value. The default inlining mode is `yes` (i.e., `#[inline]`).
`#[delegate]` to a different value. The default inlining mode is `yes` (for `#[inline]`).

```rust

Expand Down Expand Up @@ -272,7 +286,8 @@ pub fn main() {

### 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.
Delegating a trait implementation for a tuple struct **(only single-field tuples supported for now)**, for e.g. a
newtype pattern.

```rust
use ambassador::{delegatable_trait, Delegate};
Expand Down
2 changes: 1 addition & 1 deletion ambassador/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ambassador"
version = "0.5.0"
version = "0.5.1"
authors = ["Maximilian Goisser <goisser94@gmail.com>", "David Ewert <dewert10@gmail.com>"]
edition = "2018"
description = "Trait implementation delegation via procedural macros"
Expand Down
8 changes: 4 additions & 4 deletions ambassador/src/delegate_shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ impl InlineMode {

pub(crate) fn as_bracket_tokens(self) -> TokenStream2 {
match self {
InlineMode::Yes => quote!([inline]),
InlineMode::No => quote!([]),
InlineMode::Always => quote!([inline(always)]),
InlineMode::Never => quote!([inline(never)]),
InlineMode::Yes => quote!([return inline]),
InlineMode::No => quote!([return]),
InlineMode::Always => quote!([return inline(always)]),
InlineMode::Never => quote!([return inline(never)]),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions ambassador/src/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ fn delegate_single_attr(

quote! {
impl <#(#impl_generics,)*> #trait_path_full for #implementer_ident #ty_generics #where_clause {
#macro_name!{body_struct(#inline_attr <#trait_generics_p>, #field_type, #field_ident)}
#macro_name!{body_struct(#inline_attr <#trait_generics_p>, #field_type, (#field_ident), (#field_ident), (#field_ident))}
}
}
}
Expand All @@ -242,7 +242,7 @@ fn delegate_single_attr(

quote! {
impl <#(#impl_generics,)*> #trait_path_full for #implementer_ident #ty_generics #where_clause {
#macro_name!{body_struct(#inline_attr <#trait_generics_p>, #field_type, #field_ident)}
#macro_name!{body_struct(#inline_attr <#trait_generics_p>, #field_type, (#field_ident), (#field_ident), (#field_ident))}
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion ambassador/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,13 @@
//! ```
//!
//! # Backwards Compatibility
//! ### 0.5.0 -> 0.5.1
//! Libraries importing public delegatable traits cannot override inlining behaviour until their
//! dependencies updated to version 1.5.1.
//! ## 0.4.x -> 0.5.x
//! Avoiding the automatic where clause is no longer supported. The macros now call the trait method
//! specifically to avoid name collisions which prevents the use cases it used to support (see
//! https://github.com/hobofan/ambassador/issues/75). Instead, delegating to "self" can be used
//! https://github.com/hobofan/ambassador/issues/75). Instead, delegating to "self" can be used
//! if target has intrinsic methods instead of implementing the required trait, and delegating to
//! remote "deref" and "deref_mut" methods can be used if delegation implementation required
//! dereferences. For example:
Expand Down
13 changes: 5 additions & 8 deletions ambassador/src/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,7 @@ pub(crate) fn build_register_trait(
#[doc(hidden)]
macro_rules! #macro_def {
// Override arms: explicit inline specifier in brackets
(body_struct([$($inline:meta)*] <#gen_matcher>, $ty:ty, $field_ident:tt)) => {
#macro_name!{body_struct([$($inline)*] <#gen_idents_pat>, $ty, ($field_ident), ($field_ident), ($field_ident))}
};
(body_struct([$($inline:meta)*] <#gen_matcher>, $ty:ty, ($($ident_owned:tt)*), ($($ident_ref:tt)*), ($($ident_ref_mut:tt)*))) => {
(body_struct([$ret:ident $($inline:meta)*] <#gen_matcher>, $ty:ty, ($($ident_owned:tt)*), ($($ident_ref:tt)*), ($($ident_ref_mut:tt)*))) => {
#(#struct_items)*
};
// Default arms: redirect using trait-level default
Expand All @@ -108,13 +105,13 @@ pub(crate) fn build_register_trait(
(resolve_path($err:literal, $(& $(mut)?)?, $s:ident.)) => {
compile_error! {$err}
};
(body_enum([$($inline:meta)*] <#gen_matcher>, $ty:ty, ($( $other_tys:ty ),*), ($( $variants:path ),+))) => {
(body_enum([$ret:ident $($inline:meta)*] <#gen_matcher>, $ty:ty, ($( $other_tys:ty ),*), ($( $variants:path ),+))) => {
#(#enum_items)*
};
(body_enum(<#gen_matcher>, $ty:ty, ($( $other_tys:ty ),*), ($( $variants:path ),+))) => {
#macro_name!{body_enum(#default_inline_bracket <#gen_idents_pat>, $ty, ($($other_tys),*), ($($variants),+))}
};
(body_self([$($inline:meta)*] <#gen_matcher>)) => {
(body_self([$ret:ident $($inline:meta)*] <#gen_matcher>)) => {
#(#self_items)*
};
(body_self(<#gen_matcher>)) => {
Expand Down Expand Up @@ -410,9 +407,9 @@ fn build_method_invocation(
};

let mut method_invocation = if use_trait_method {
quote! { #trait_ident::#method_ident::<#(#generics,)*>(#field, #argument_list) #post }
quote! { $ret #trait_ident::#method_ident::<#(#generics,)*>(#field, #argument_list) #post }
} else {
quote! { #field.#method_ident::<#(#generics,)*>(#argument_list) #post }
quote! { $ret #field.#method_ident::<#(#generics,)*>(#argument_list) #post }
};
if original_method.sig.unsafety.is_some() {
method_invocation = quote! (
Expand Down
Loading