Coder Social home page Coder Social logo

dansnow / rust-delegate Goto Github PK

View Code? Open in Web Editor NEW

This project forked from kobzol/rust-delegate

0.0 2.0 0.0 48 KB

Rust method delegation with less boilerplate

Home Page: https://crates.io/crates/delegate

License: Apache License 2.0

Rust 100.00%

rust-delegate's Introduction

Method delegation with less boilerplate

Build Status Crates.io

This crate helps remove some boilerplate for structs that simply delegates most of its methods to one or more fields.

For example, let's say we want to implement a stack in Rust. In case you aren't familiar with the idea, a stack is a data structure in which items are inserted and accessed in a LIFO (Last-In, First-Out) manner. Typically, a stack supports the following basic operations:

  • push: insert an item to the top of the stack.
  • pop: remove an item from the top of the stack (if stack is not empty).

In addition, a stack may support secondary operations such as querying if the stack is empty, the current size of the stack, a peek operation that gives access to the top item without removing it, a method to clear the stack and so on.

One way to implement such a data structure in Rust would be to use a Vec:

#[derive(Clone, Debug)]
struct Stack<T> {
    inner: Vec<T>,
}

impl<T> Stack<T> {
    /// Allocate an empty stack
    pub fn new() -> Stack<T> {
        Stack { inner: vec![] }
    }

    /// The number of items in the stack
    pub fn size(&self) -> usize {
        self.inner.len()
    }

    /// Whether the stack is empty
    pub fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }

    /// Push an item to the top of the stack
    pub fn push(&mut self, value: T) {
        self.inner.push(value)
    }

    /// Remove an item from the top of the stack
    pub fn pop(&mut self) -> Option<T> {
        self.inner.pop()
    }

    /// Accessing the top item without removing it from the stack
    pub fn peek(&self) -> Option<&T> {
        self.inner.last()
    }

    /// Remove all items from the stack
    pub fn clear(&mut self) {
        self.inner.clear()
    }
}

As you can see, Vec already supports most of the operations we needed, so in most cases our implementation is simply delegating to the underlying Vec, except occasionally re-mapping the method to a different name (e.g. peek() is called last() in Vec).

The fact that these implementations are boring (simply delegating to another struct) is probably notable and worth calling out. If the reader of the code is already familiar with the behavior with the other struct, they can safely gloss over these methods and focus on the more interesting ones. Further more, if we can trust that the struct we are delegating to has a solid implementation and is well tested, we can probably just write a simple smoke test and not worry about re-testing the edge cases.

Unfortunately, this detail could easily get lost, especially when these methods are buried within other non-delegating methods. The only way to be sure is to carefully read the implementation to confirm that they aren't doing anything more, which somewhat defeats the purpose.

The delegate! macro in this crate helps solve this problem by making your delegating methods more declarative:

use delegate::delegate;

#[derive(Clone, Debug)]
struct Stack<T> {
    inner: Vec<T>,
}

impl<T> Stack<T> {
    pub fn new() -> Stack<T> {
        Stack { inner: vec![] }
    }

    delegate! {
        to self.inner {
            /// The number of items in the stack
            #[target_method(len)]
            pub fn size(&self) -> usize;

            /// Whether the stack is empty
            pub fn is_empty(&self) -> bool;

            /// Push an item to the top of the stack
            pub fn push(&mut self, value: T);

            /// Remove an item from the top of the stack
            pub fn pop(&mut self) -> Option<T>;

            /// Accessing the top item without removing it from the stack
            #[target_method(last)]
            pub fn peek(&self) -> Option<&T>;

            /// Remove all items from the stack
            pub fn clear(&mut self);
        }
    }
}

This macro invocation would generate exactly the same code as we had written by hand in the example above (with one minor difference, see below). Not only did you save a few lines of typing, you are making your intent more clear to your readers as well.

The macro support all the usual syntactic elements that are valid around method declarations, such as (doc) comments, attributes, pub modifiers, generics, lifetimes, return type and where clauses. The only difference is that instead of providing a block for the method body, you simply end it with a ; after the method signature. The macro will automatically generate a suitable body.

The macro will also automatically insert the #[inline] hint for the compiler, which is often desirable for delegating methods. You can override this by inserting an explicit #[inline] attribute (such as #[inline(always)] or #[inline(never)]).

As seen in the example above, if the name of the method does not match, you can override the inferred name (same name as your struct method) with the custom #[target_method(...)] attribute. (This attribute is removed by the macro during expansion, so it does not rely on the experimental "custom_attribute" feature.)

You may also delegate different methods to different fields inside the same delegate! block. For example:

use delegate::delegate;

#[derive(Clone, Debug)]
struct MultiStack<T> {
    left: Vec<T>,
    right: Vec<T>,
}

impl<T> MultiStack<T> {
    pub fn new() -> MultiStack<T> {
        MultiStack { left: vec![], right: vec![] }
    }

    delegate! {
        to self.left {
            /// Push an item to the top of the left stack
            #[target_method(push)]
            pub fn push_left(&mut self, value: T);

            /// Remove an item from the top of the left stack
            #[target_method(pop)]
            pub fn pop_left(&mut self, value: T);
        }
        to self.right {
            /// Push an item to the top of the right stack
            #[target_method(push)]
            pub fn push_right(&mut self, value: T);

            /// Remove an item from the top of the right stack
            #[target_method(pop)]
            pub fn pop_right(&mut self, value: T);
        }
    }
}

The expression after to can also be a nested field or even a function/method call:

struct Inner;
impl Inner {
    pub fn method(&self, num: u32) -> u32 {
        num
    }
    pub fn method2(&self, num: u32) -> u32 {
        num
    }
}

struct Inner2 {
    inner: Inner
}

struct Wrapper {
    inner: Inner2,
    mutex: std::sync::Mutex<Inner>
}

fn global_fn(num: u32) -> u32 { num }

impl Wrapper {
    delegate! {
        target self.inner.inner {
            pub(crate) fn method(&self, num: u32) -> u32;
        }
        target self.mutex.lock().unwrap() {
            fn method2(&self, num: u32) -> u32
        }
        target global_fn
    }
}

You can change the return type of the delegated method. If you don't want to your delegating method to return anything, you can omit the return type from the declaration. This will cause the method to return (). Or you can choose a different datatype which can be converted using the From trait from the original datatype.

struct Inner;
impl Inner {
    pub fn method1(&self, num: u32) -> u32 { num }
    pub fn method2(&self, num: u32) -> u32 { num }
}
struct Wrapper { inner: Inner }
impl Wrapper {
    delegate! {
        to self.inner.inner {
            pub fn method(&self, num: u32);
            pub fn method2(&self, num: u32) -> u64;
        }
    }
}

The delegate! macro is implemented as a procedural macro.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Conduct

Please follow the Rust Code of Conduct. For escalation or moderation issues please contact the crate author(s) listed in Cargo.toml.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.