Another Rust spot: delegation

Posted on January 21, 2016 by Tommy McGuire

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:

Write a bunch of trivial methods for Extended like, pub fn foo(&self) -> { self.inner.foo() } But that is rather tedious.

Wait for a compiler extension or change to the Rust language to support a “delegate” attribute or something. Like maybe RFC 292: allow delegating some methods from an trait impl to a field of a struct #292. Now, I’m lazy, so that’s not a bad plan, but still, I would like to get some work done now.

Drag out some macro-fu, polish it up bright and shiny, and lay waste to every village and small township between here and the sea! Remember: pillage, then burn. There are undoubtedly some weaknesses here, and it’s going to have to be a bit more verbose than the second option (and I won’t get a nap), but it’s what I’m doing. Otherwise, this wouldn’t be much of a blog post, would it?

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:

The field that is the destination of the delegation (with this macro, it has to be a named field; structures with a single anonymous “.0” name aren’t supported),

The name of the method,

The types (and names) of the method’s arguments and return value, and

A couple of markers that I’ll get into below.

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.

The first rule is a simple default which expands to nothing if the body of the invocation is empty (or commented out).

The remainder of the rules are in four pairs: One with no extra markers, as in the example above, One with a pub marker, producing a method with pub visibility, One with a mut marker, producing a method where the reference to self is mutable, and One with both pub and mut markers.

Each of the pairs consists of a base case, producing a single method as an expansion, and a recursive case, producing a method and then re-invoking the macro on the next method type specification.

In each rule, $fld is an identifier, the field specifier (which only needs to be given once; it’s passed along to the recursive calls). $fcn is another identifier and is the function name. $a (an identifier) and $at (a type) are the argument names and types of any method arguments (other than self ). $r is the return type of the method. I wonder if you can specify () as the return type to get a procedure that does not return anything. Possibly another weakness of this monstrosity.

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.

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.

A WIBNI is sort of like a RFC proposal or something; the difference is that I have no idea how to do it.

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

Please enable JavaScript to view the comments powered by Disqus.