Another Rust spot: delegation
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
Extendedlike,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
pubmarker, producing a method withpubvisibility,One with a
mutmarker, producing a method where the reference toselfis mutable, andOne with both
pubandmutmarkers.
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,
$fldis an identifier, the field specifier (which only needs to be given once; it’s passed along to the recursive calls).$fcnis 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 thanself).$ris 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.
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.
- 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’
