Library / SDK
Kobzol/rust-delegate avatar
Kobzol/rust-delegate

rust-delegate generates forwarding methods so a wrapper type stops repeating itself

Rust method delegation with less boilerplate

529 stars23 forksRustApache-2.0

At a glance

What is it?
rust-delegate is a proc macro crate for Rust that writes forwarding method bodies for you, with attributes for renaming, changing the called method, converting the return type, awaiting async calls, wrapping the delegated expression and passing extra arguments. It suits a codebase with many newtype wrappers, and it is a code generator with a small, fixed set of shapes.
Who is it for?
Adopt rust-delegate if your Rust codebase is full of newtype and wrapper structs whose only job is to forward calls to an inner field, because the macro writes the method bodies for you and keeps the call sites in the wrapper explicit rather than hiding them behind Deref.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The boilerplate this macro removes

rust-delegate is a crate that removes some boilerplate for structs that simply delegate some of their methods to one or more of their fields, and it gives you the delegate! macro, which delegates method calls to selected expressions, usually inner fields. The canonical example in the README is a Stack data structure implemented using an inner Vec via delegation.

rust
use delegate::delegate;

#[derive(Clone, Debug)]
struct Stack<T> {
    inner: Vec<T>,
}
impl<T> Stack<T> {
    pub fn new() -> Self <T> {
        Self { inner: vec![] }
    }

    delegate! {
        to self.inner {
            pub fn is_empty(&self) -> bool;
            pub fn push(&mut self, value: T);
            pub fn pop(&mut self) -> Option<T>;
            pub fn clear(&mut self);

            #[call(len)]
            pub fn size(&self) -> usize;

            #[call(last)]
            pub fn peek(&self) -> Option<&T>;

        }
    }
}

The point is the size method and the peek method. Without the macro, size would be a hand written function calling len on the inner vector and peek would call last, and each of those is three lines of signature and body that has to be kept in sync with the inner type's API. The macro also inserts #[inline(always)] automatically, and the README notes that you can override that decision by specifying #[inline] manually on the delegated method.

Renaming, arbitrary targets and enum variants

Three forms of targeting are documented. The first is delegating to a method with a different name, using the call attribute.

rust
struct Stack {
    inner: Vec<u32>
}
impl Stack {
    delegate! {
        to self.inner {
            #[call(push)]
            pub fn add(&mut self, value: u32);
        }
    }
}

The second is delegating to an arbitrary inner field expression rather than a plain field path, which is how you reach through a RefCell or an Rc.

rust
struct Wrapper {
    inner: Rc<RefCell<Vec<u32>>>
}
impl Wrapper {
    delegate! {
        to self.inner.deref().borrow_mut() {
            pub fn push(&mut self, val: u32);
        }
    }
}

The third is delegating to enum variants, where the macro expands to a match over the enum and calls the same method on each variant. The README writes out the transformation in a comment so the generated code is visible rather than magic.

That last form is the one with the sharpest edge, since the body of each match arm runs on every call, and an arm that prints will print on every delegation.

Modifiers that change the generated body

A set of attributes alters what the generated method body does after the delegated call. The into attribute converts the result using From, try_into converts it using TryFrom, unwrap unwraps a Result, and call names the method to call when it differs from the generated name. The README shows them combined.

rust
impl Wrapper {
    delegate! {
        to self.inner {
            // calls method, converts result to u64 using `From`
            #[into]
            pub fn method(&self, num: u32) -> u64;

            // calls method, returns ()
            #[call(method)]
            pub fn method_noreturn(&self, num: u32);

            // calls method, converts result to i6 using `TryFrom`
            #[try_into]
            #[call(method)]
            pub fn method2(&self, num: u32) -> Result<u16, std::num::TryFromIntError>;

            // calls method_res, unwraps the result
            #[unwrap]
            pub fn method_res(&self, num: u32) -> u32;

            // calls method_res, unwraps the result, then calls into
            #[unwrap]
            #[into]
            #[call(method_res)]
            pub fn method_res_into(&self, num: u32) -> u64;

            // specify explicit type for into
            #[into(u64)]
            #[call(method)]
            pub fn method_into_explicit(&self, num: u32) -> u64;
        }
    }
}

The explicit type form of into, written #[into(u64)], is there because inference cannot always pick the target type when a conversion is involved. The unwrap attribute deserves a note: it moves a Result out of the inner type and into a panic on error, which converts a recoverable failure into a crash, and that is a decision you should make in the wrapper deliberately rather than inherit from a macro.

Custom expressions and extra arguments

The expr attribute modifies the delegated call itself. The README explains that you can use the dollar sigil as a placeholder for what delegate would normally expand to, and wrap that expression with custom code, and that the placeholder is not required and can be present multiple times if you want.

rust
struct A(Vec<u8>);

impl A {
    delegate! {
        to self.0 {
            #[expr(*$.unwrap())]
            /// Here `$` == `self.0.get(idx)`
            /// Will expand to `*self.0.get(idx).unwrap()`
            fn get(&self, idx: usize) -> u8;

            #[call(get)]
            #[expr($?.checked_pow(2))]
            /// Here `$` == `self.0.get(idx)`
            /// Will expand to `self.0.get(idx)?.checked_pow(2)`
            fn get_checked_pow_2(&self, idx: usize) -> Option<u8>;
        }
    }
}

A second capability is adding arguments the inner method does not take, which the README demonstrates with a wrapper that holds a OnceCell and constructs its inner value lazily. The delegated target is a closure that takes the extra argument.

rust
impl Wrapper {
    delegate! {
        to |k: u32| self.content(k) {
            // `wrapper.method(k, num)` will call `self.content(k).method(num)`
            pub fn method(&self, num: u32) -> u32;
        }
    }
}

That pattern is the useful one for lazy wrappers, because the argument is passed at the call site and consumed by the closure that produces the inner value, so the wrapper can present a two argument API over a one argument inner type.

Async delegation and the await attribute

Async methods are supported, and the await attribute controls whether .await is generated after the delegated expression.

rust
impl Wrapper {
    delegate! {
        to self.inner {
            // calls method(num).await, returns impl Future<Output = u32>
            pub async fn method(&self, num: u32) -> u32;
            // calls method(num).await.into(), returns impl Future<Output = u64>
            #[into]
            #[call(method)]
            pub async fn method_into(&self, num: u32) -> u64;
        }
    }
}

The README states that you can use the #[await(true/false)] attribute on delegated methods to specify if .await should be generated after the delegated expression, and that it will be generated by default if the delegated method is async. So the attribute exists for the case where you want the opposite of the default, and the combination of await with into is how an async method can return a converted value rather than the inner type's own return type. The dev dependencies give a sense of how this is exercised, with tokio, futures and async-trait in the test set.

Package shape, licence and the macro test suite

The crate is published on crates.io under the name delegate, with the homepage pointing at crates.io/crates/delegate and the repository at github.com/kobzol/rust-delegate. The Cargo.toml declares version 0.13.5, edition 2018, and the licence as MIT OR Apache-2.0, with both LICENSE-APACHE and LICENSE-MIT files in the tree, so you can take it under either. It is a proc macro crate, declared with proc-macro = true, and it depends on syn with the full and visit-mut features, quote and proc-macro2, which is the standard parsing and code generation stack for a Rust macro. The authors are listed as Godfrey Chan and Jakub Beranek. The dev dependencies include macrotest, which is how this kind of crate is normally tested: the macro output is compared against expected expansions, so a change in the generated code is caught by the test suite rather than by a reader. The release history is short, with v0.13.5 published on 2025-11-17 and the last push to the repository on 2026-04-12, and the project uses release-plz for versioning with a CHANGELOG.md in the tree.

Where rust-delegate is the wrong choice

Four limits are worth stating. The first is scope. The macro covers a fixed set of shapes: forward to a field, forward to a call target with a different name, forward to an expression, convert the result, unwrap it, await it, add arguments. Anything outside that list, a delegated method that needs a precondition check, a retry, a lock or a log line, does not fit and you write the method by hand. The second is that it competes with Deref. A wrapper type that exists to expose the inner type's API can implement Deref and get the same effect without a macro, and where the wrapper is meant to be transparent that is the more idiomatic answer, whereas the macro is better when you want the forwarding to be explicit at every call site. The third is the failure mode of generated code. Because the method bodies are produced at compile time, a mistake in the macro shows up as a compile error somewhere else in your crate rather than at the delegation site, and the macrotest suite is what protects you from a change in behaviour that still compiles. The fourth is maintenance shape. The crate is small and stable, the release history has one recent version, and it is a proc macro dependency, so a compile time cost and a supply chain surface come with it, and a crate with a 0.x version number means the interface can still move. The realistic alternative to weigh is writing the forwarding methods by hand, which is verbose but obvious, or reaching for Deref and AsRef, which removes the boilerplate entirely at the cost of implicit conversion, and for a codebase with a handful of wrappers the hand written version is usually the one a reviewer will prefer.

Editorial conclusion

Adopt rust-delegate if your Rust codebase is full of newtype and wrapper structs whose only job is to forward calls to an inner field, because the macro writes the method bodies for you and keeps the call sites in the wrapper explicit rather than hiding them behind Deref. Do not adopt it if you have only a handful of such wrappers, because a macro adds a dependency and a layer of generated code for something two lines of Rust would express, and do not adopt it if you want to reach the inner field generically, since Deref and AsRef are the better tools for that. Verify two things before you commit: which Rust edition and toolchain your project pins, because the crate declares edition 2018 and its release history is thin, and whether the attribute you need is one of the documented ones, since the crate covers renaming, call target, conversion, await and custom expressions but not arbitrary statements. The practical first step is to add the delegate dependency to one wrapper type with two or three forwarded methods and read the expansion before you use it across a module.

Frequently asked questions

What is rust-delegate?

It is a Rust proc macro crate, published on crates.io as delegate, that removes boilerplate for structs that delegate some of their methods to one or more of their fields. It provides the delegate! macro, which delegates method calls to selected expressions, usually inner fields.

How do I install rust-delegate?

The crate is published on crates.io as delegate, so you add it to the dependencies section of your Cargo.toml and it is fetched by cargo. The package metadata declares edition 2018, version 0.13.5 and the licence MIT OR Apache-2.0.

Can rust-delegate rename the delegated method?

Yes. The call attribute names the method to invoke on the target, so a generated push_left can call push on the inner vector. There is also an into attribute with an explicit type form, such as into(u64), when conversion needs a target type that inference cannot pick.

Does rust-delegate support async methods?

Yes. Delegated methods can be declared async, and the await attribute can be set to true or false to control whether .await is generated after the delegated expression. The README states it is generated by default if the delegated method is async.

What licence is rust-delegate released under?

The Cargo.toml declares MIT OR Apache-2.0, and the repository carries both a LICENSE-APACHE and a LICENSE-MIT file. The project is maintained by Godfrey Chan and Jakub Beranek, and the latest published version is 0.13.5 from 2025-11-17.

Official sources

  1. Kobzol/rust-delegate on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/kobzol-rust-delegate.svg)](https://hysenlabs.com/projects/kobzol-rust-delegate)

Was this useful?