Ambassador

repository·master·Indexed 43 days ago

https://github.com/hobofan/ambassador

A Rust library (v0.5.1) that uses procedural macros to automate trait implementation delegation for structs and enums. It reduces boilerplate by allowing developers to mark traits as delegatable with #[delegatable_trait] and derive implementations using #[derive(Delegate)], supporting custom targets, generic bounds, remote traits, and remote types.

Tokens
3.5K
Snippets
16
Records
16
Agent score
5%

What's inside Ambassador

  1. How Ambassador delegation works

    master

    Ambassador uses procedural macros to eliminate boilerplate when delegating trait implementations to struct fields or enum variants.

    The workflow involves two main steps:

    1. Mark the trait: Use #[delegatable_trait] on the trait definition to make it available for delegation.
    2. Derive the delegation: Use #[derive(Delegate)] and #[delegate(TraitName)] on the type (struct or enum) that should receive the implementation.
    use ambassador::delegatable_trait;
    
    #[delegatable_trait]
    pub trait Shout {
        fn shout(&self, input: &str) -> String;
    }
    
    #[derive(ambassador::Delegate)]
    #[delegate(Shout)]
    pub struct WrappedCat(Cat);
  2. Delegate remote types with `#[delegate_remote]`

    master

    If you want to delegate to a type defined in an external crate, copy its definition into your code and mark it with #[delegate_remote].

    Note: You do not need to include all fields in the copy; only include the fields you are delegating to. Because of the orphan rule, you cannot combine #[delegatable_trait_remote] and #[delegate_remote] on the same item.

    #[delegate_remote]
    #[delegate(Shout, target = "bar")]
    struct WrappedAnimals<A: Shout> {
        bar: A,
    }
  3. Delegate generic traits

    master

    To delegate traits with generics, use the generics key in #[delegate]. When using X or 'x followed by digits (e.g., X0, 'x3), they are treated as maximally generic. The macro automatically adds a where clause to ensure validity for the inner type.

    #[delegatable_trait]
    pub trait Shout<T> {
        fn shout(&self, input: T) -> String;
    }
    
    #[derive(Delegate)]
    #[delegate(Shout<X>, generics = "X")]
    pub struct WrappedCat(Cat);
  4. Delegate to self with `target = "self"`

    master

    If a type already implements the methods of a trait (but doesn't implement the trait itself), you can use target = "self" to satisfy the trait requirement without manual boilerplate.

    Note: This does not work for traits with associated types or constants. You must explicitly add any required where clauses using the where key.

    #[derive(Delegate)]
    #[delegate(Shout, target = "self")]
    pub struct Cat;
    
    impl Cat {
        fn shout(&self, input: &str) -> String {
            format!("{} - meow!", input)
        }
    }
  5. Make a trait delegatable with `#[delegatable_trait]`

    master

    To allow a trait to be delegated to other types, you must annotate it with #[delegatable_trait]. This also makes the trait delegatable in other crates.

    use ambassador::delegatable_trait;
    
    #[delegatable_trait]
    pub trait Shout {
        fn shout(&self, input: &str) -> String;
    }
  6. Specify a delegation target with `target`

    master

    By default, Ambassador delegates to the first field or variant. Use the target key in the #[delegate] attribute to specify a different target:

    • For structs: Use the field name (e.g., target = "foo").
    • For tuple structs: Use the index as a string (e.g., target = "1").
    #[derive(Delegate)]
    #[delegate(Shout, target = "foo")]
    pub struct WrappedCats {
        foo: Cat,
        bar: Cat,
    }
    
    #[derive(Delegate)]
    #[delegate(Shout, target = "1")]
    pub struct WrappedAnimals(Cat, Dog);
  7. Control method inlining

    master

    You can control the inlining behavior of delegated methods using the inline key.

    Available values:

    • yes: Adds #[inline] (this is the default).
    • no: No inlining attribute.
    • always: Adds #[inline(always)].
    • never: Adds #[inline(never)].

    You can set a default inlining mode for a trait using #[delegatable_trait(inline = "...")] and override it for specific delegations using #[delegate(..., inline = "...")].

    #[delegatable_trait(inline = "always")]
    pub trait Shout {
        fn shout(&self, input: &str) -> String;
    }
    
    #[derive(Delegate)]
    #[delegate(Shout)] // Uses "always"
    pub struct WrappedDog(Dog);
    
    #[derive(Delegate)]
    #[delegate(Shout, inline = "never")] // Overrides to "never"
    pub struct WrappedCat(Cat);
  8. Delegate remote traits with `#[delegatable_trait_remote]`

    master

    If you cannot modify a trait (it's defined in an external crate), you can make it delegatable by copy-pasting its signature into your crate and marking it with #[delegatable_trait_remote].

    use ambassador::delegatable_trait_remote;
    use std::fmt::Display;
    
    #[delegatable_trait_remote]
    trait Display {
        fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error>;
    }
    
    #[derive(Delegate)]
    #[delegate(Display)]
    pub struct WrappedCat(Cat);
  9. Add generic bounds with the `where` key

    master

    Use the where key in the #[delegate] attribute to apply specific generic bounds to the delegation. Ambassador automatically adds a where clause to ensure the target field implements the trait, but you can refine this manually.

    #[derive(Delegate)]
    #[delegate(Shout, where = "A: Debug")]
    pub struct WrappedFoo<A> {
        foo: A,
    }
  10. Delegate a trait using `#[derive(Delegate)]`

    master

    Apply #[derive(Delegate)] to a struct or enum, and use #[delegate(TraitName)] to specify which trait to implement via delegation.

    use ambassador::Delegate;
    
    pub struct Cat;
    
    impl Shout for Cat {
        fn shout(&self, input: &str) -> String {
            format!("{} - meow!", input)
        }
    }
    
    #[derive(Delegate)]
    #[delegate(Shout)]
    pub struct WrappedCat(Cat);
  11. Redefine a remote type for delegation with `#[delegate_remote]`

    master

    Use #[delegate_remote] to make an existing type from another crate delegate traits to its members. This is achieved by copy-pasting the type's definition into your code under this attribute.

    If the type is a struct, you only need to include the fields that are being delegated to; other fields can be omitted.

    use ambassador::{delegate_remote, delegatable_trait};
    
    #[delegatable_trait]
    pub trait Shout { fn shout(&self, input: &str) -> String; }
    
    // Assume Cat is defined elsewhere
    
    #[delegate_remote]
    #[delegate(Shout, target = "bar")]
    struct WrappedAnimals<A, B> {
        foo: A,
        bar: B,
        // baz: u32, // Private or unused fields can be omitted
    }