Another Rust spot: delegation

Posted on January 21, 2016 by Tommy McGuire
Labels: rust, programming language

Yeah, I know: I lied. In the last post, I wrote, “Next time: given the file contents, can we diddle-about with word-like tokens in it without copying strings around?” and yet I’m not writing about that now. Sorry. Maybe next time. This seemed more important.

Anyway, here’s the deal: suppose you have a type Basic which has a few methods and perhaps an implementation of a trait:

struct Basic {
    some_field: usize,
}

impl Basic {
    pub fn new() -> Basic { Basic { some_field: 4} }
    pub fn foo(&self) -> usize { self.some_field - 1 }
    pub fn bar(&self, i: usize) -> f64 { self.some_field as f64 / i as f64 }
    pub fn baz(&mut self) -> usize { self.some_field }
}

trait A {
    fn quux(&self) -> usize;
}

impl A for Basic {
    fn quux(&self) -> usize { self.some_field }
}

And you have a type, Extended, that has a field of type Basic:

pub struct Extended {
    inner: Basic,
    _red_herring: usize,
}

impl Extended {
    pub fn new() -> Extended {
        Extended { inner: Basic::new(), _red_herring: 12 } }
}

What you want is for Extended to support the same set of methods (and the trait) as Basic, with the methods being delegated to the Basic field.

There are, currently, three ways to deal with this:

The macro

Without further ado, here’s the macro:

    #[macro_export]
    macro_rules! delegate {
        ( $fld:ident : ) => {
        };

        ( $fld:ident : $fcn:ident ( $( $a:ident : $at:ty ),* ) -> $r:ty ) => {
            fn $fcn ( &self, $( $a : $at ),* ) -> $r {
                (self.$fld).$fcn( $( $a ),* )
            }
        };

        ( $fld:ident : $fcn:ident ( $( $a:ident : $at:ty ),* ) -> $r:ty, $($rest:tt)* ) => {
            fn $fcn ( &self, $( $a : $at ),* ) -> $r {
                (self.$fld).$fcn( $( $a ),* )
            }
            delegate!($fld : $($rest)*);
        };

        ( $fld:ident : pub $fcn:ident ( $( $a:ident : $at:ty ),* ) -> $r:ty ) => {
            pub fn $fcn ( &self, $( $a : $at ),* ) -> $r {
                (self.$fld).$fcn( $( $a ),* )
            }
        };

        ( $fld:ident : pub $fcn:ident ( $( $a:ident : $at:ty ),* ) -> $r:ty, $($rest:tt)* ) => {
            pub fn $fcn ( &self, $( $a : $at ),* ) -> $r {
                (self.$fld).$fcn( $( $a ),* )
            }
            delegate!($fld : $($rest)*);
        };

        ( $fld:ident : mut $fcn:ident ( $( $a:ident : $at:ty ),* ) -> $r:ty ) => {
            fn $fcn ( &mut self, $( $a : $at ),* ) -> $r {
                (self.$fld).$fcn( $( $a ),* )
            }
        };

        ( $fld:ident : mut $fcn:ident ( $( $a:ident : $at:ty ),* ) -> $r:ty, $($rest:tt)* ) => {
            fn $fcn ( &mut self, $( $a : $at ),* ) -> $r {
                (self.$fld).$fcn( $( $a ),* )
            }
            delegate!($fld : $($rest)*);
        };

        ( $fld:ident : pub mut $fcn:ident ( $( $a:ident : $at:ty ),* ) -> $r:ty ) => {
            pub fn $fcn ( &mut self, $( $a : $at ),* ) -> $r {
                (self.$fld).$fcn( $( $a ),* )
            }
        };

        ( $fld:ident : pub mut $fcn:ident ( $( $a:ident : $at:ty ),* ) -> $r:ty, $($rest:tt)* ) => {
            pub fn $fcn ( &mut self, $( $a : $at ),* ) -> $r {
                (self.$fld).$fcn( $( $a ),* )
            }
            delegate!($fld : $($rest)*);
        };

    }

Whew. I haven’t written that many funny characters since the last time I did Perl professionally.

This is actually a pretty simple recursive macro with a great number of repeated elements. A basic invocation would be:

    delegate!{
        inner:
        foo() -> usize
    }

This little guy produces:

    fn foo(&self) -> usize { inner.foo() }

What the macro does is to take the name of a field as a first argument and a list of simplified method declarations as the remaining arguments, and then produces a sequence of method definitions that delegate the matching methods to the field.

The minimum information required to generate the definitions is:

Since this is a simple syntax extension, all of those have to be present in the call to delegate. Let me break down the macro.

Finally, in the recursive rules, $rest is the remainder of the macro invocation. To tell the truth, I’m not entirely sure what a tt is or why you have to have a sequence of them for the recursive calls; I copied it from the example in the book. Yay, cargo-culting!

Finally, here are some examples of it in use.

    impl Extended {
        delegate!{
            inner:
            pub foo() -> usize,
            pub bar(i:usize) -> f64,
            pub mut baz() -> usize
        }
    }

This provides delegate methods for Extended, for methods foo, bar, and baz; the last of which needs a mutable reference to self.

    impl A for Extended {
        delegate!{
            inner:
            quux() -> usize
        }
    }

This call delegates quux and provides an implementation of the A trait, as well as demonstrating a case where you don’t want pub: it gets an error in a trait implementation.

Comments

Could be nice to automatically take a trait implemented for Basic and use a macro to delegate it all to Extended, not method by method.

Anonymous ‘2016-01-23T12:39:45.402-06:00’

@Anonymous: unfortunately that’s impossible! Macros operate on syntax only, no access to types or trait lookups. Possibly you could wrap a macro around the entire trait definition, but it would be a monstrosity (and wouldn’t work for remote traits).

durka ‘2016-01-23T15:58:01.720-06:00’

@durka, exactly.

Wouldn’t It Be Neat If (WIBNI*) macros could examine existing Rust code, like being able to find information about a trait’s methods? Hmm.

I assume compiler extensions for things like derive can (and do) do this already, but I haven’t even glanced at how to write those.

Tommy McGuire ‘2016-01-23T20:51:34.973-06:00’

active directory applied formal logic ashurbanipal authentication books c c++ comics conference continuations coq data structure digital humanities Dijkstra eclipse virgo electronics emacs goodreads haskell http java job Knuth ldap link linux lisp math naming nimrod notation OpenAM osgi parsing pony programming language protocols python quote R random REST ruby rust SAML scala scheme shell software development system administration theory tip toy problems unix vmware yeti
Member of The Internet Defense League
Site proudly generated by Hakyll.