Crate thin_delegate

Source
Expand description

Auto implementation of trait functions by delegation to inner types

This crate provides attribute macros that supports to define trait functions by delegation to inner types.

  • #[thin_delegate::register]: Registers definitions of trait, struct and enum.
  • #[thin_delegate::fill_delegate]: Derives and fills impl Trait for StructEnum by delegation.
  • #[thin_delegate::external_trait_def]: Imports trait definitions in external crates.

There exist similar crates. See comparison for more details.

See also related RFCs: rfcs#1406, rfcs#2393, rfcs#3530.

§Example

#[thin_delegate::register]
trait AnimalI {
    fn sound(&self) -> String;
    fn walk(&mut self, pos: usize) -> usize;
}

#[thin_delegate::register]
struct Duck(String);

#[thin_delegate::register]
struct Cat {
    sound: String,
    speed: usize,
}

#[thin_delegate::register]
enum Animal {
    Duck(Duck),
    Cat(Cat),
}

// Implement delegatee manually.
impl AnimalI for String {
    fn sound(&self) -> String {
        self.clone()
    }

    // String doesn't walk.
    fn walk(&mut self, _pos: usize) -> usize {
        unimplemented!();
    }
}

// Delegate all methods to `String`. Leave `walk()` umimplemented.
// Delegation of a struct with single field is automatic.
#[thin_delegate::fill_delegate]
impl AnimalI for Duck {}

// Delegate `sound()` to `sound: String`. Implement `walk()` manually.
// Delegation of a struct with multiple fields is ambiguous. Needs to designate `scheme`.
#[thin_delegate::fill_delegate(scheme = |f| f(&self.sound))]
impl AnimalI for Cat {
    fn walk(&mut self, pos: usize) -> usize {
        pos + self.speed
    }
}

// Delegate all methods to each arms `Duck` and `Cat`.
// Delegation of an enum is automatic.
#[thin_delegate::fill_delegate]
impl AnimalI for Animal {}

let duck = Duck("quack".to_string());
let mut cat = Cat { sound: "mew".to_string(), speed: 1 };
let mut neko = Cat { sound: "nya-nya-".to_string(), speed: 2 };
assert_eq!(duck.sound(), "quack");
assert_eq!(cat.sound(), "mew");
assert_eq!(cat.walk(10), 11);
assert_eq!(neko.sound(), "nya-nya-");
assert_eq!(neko.walk(10), 12);
let duck = Animal::Duck(duck);
let mut cat = Animal::Cat(cat);
let mut neko = Animal::Cat(neko);
assert_eq!(duck.sound(), "quack");
assert_eq!(cat.sound(), "mew");
assert_eq!(cat.walk(10), 11);
assert_eq!(neko.sound(), "nya-nya-");
assert_eq!(neko.walk(10), 12);

See tests for more examples and sabiniwm for real world examples.

§How it works

  1. #[thin_delegate::register] defines a declarative macro for each trait/struct/enum definition.
  2. #[thin_delegate::fill_delegate] collects related definitions by using those declarative macros and CPS, and then calls an attribute macro #[thin_delegate::__internal__fill_delegate].
  3. #[thin_delegate::__internal__fill_delegate] fills impl Trait for StructEnum {...}.

See src/decl_macro.rs for more details.

§FAQ

§What is an error like error: cannot find macro `__thin_delegate__feed_trait_def_of_Hello` in this scope?

In the above step 2, #[thin_delegate::fill_delegate] needs some declarative macros. This error reports that rustc couldn’t find the macro.

Recommended actions:

  • Make sure that your trait/struct/enum is qualified with #[thin_delegate::register] correctly.
  • If you are using an external trait definition, make sure that a path of a module is given by an argument external_trait_def of #[thin_delegate::fill_delegate] and the module is qualified with #[thin_delegate::external_trait_def].

See fail_register_for_*.rs in tests for the exact error messages.

§Limitations

There are three types of limitations of thin_delegate.

  • (Normal) limitation
    • It’s not feasible to support them.
    • fail_limitation_*.rs in tests.
  • Intended limitation
    • It shouldn’t be supported because there is no way to avoid ambiguity or something wrong.
    • fail_intended_limitation_*.rs in tests.
  • Weak limitation
    • It’s not feasible to support them, but it would be not a problem in practice.
    • fail_weak_limitation_*.rs in tests.

§Performance

Note that using enum is more performant than Box<dyn Trait> in general case. (The main reason is not using vtable. One can expect branch prediction works for match in most-inner loops.) See also benchmark of enum_dispatch. It would be an option if you need just a polymorphism closed in your application. See Backend in sabiniwm for example (while the main reason for using enum is not performance in this case).

§Comparison

§enum_dispatch

  • Limitations
    • Doesn’t support, e.g. external traits and generics.
  • Implementation uses not safe mechanism (Using global variable in proc macro)

See also documentation of enum_delegate.

§enum_delegate (< v0.3.0)

See also limitations.

§enum_delegate (>= v0.3.0)

See also limitations.

§auto-delegate

§ambassader

  • Competitive. I recommend it if you doesn’t need features/APIs of thin_delegate.

§portrait

  • Exposes a macro with the same name to the struct/enum.

Attribute Macros§

external_trait_def
An attribute macro marking a module as “external trait definitions”
fill_delegate
An attribute macro deriving impl by delegation to an inner field
register
An attribute macro registering a definition of trait/struct/enum for #[thin_delegate::fill_delegate]