Skip to content

Instantly share code, notes, and snippets.

@DanielKeep
Created January 4, 2015 05:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DanielKeep/bf96bcf481b9e51acb18 to your computer and use it in GitHub Desktop.
Save DanielKeep/bf96bcf481b9e51acb18 to your computer and use it in GitHub Desktop.
Pre-RFC: Method Macro Syntax
  • Start Date: 2014-11-30
  • RFC PR: (leave this empty)
  • Rust Issue: (leave this empty)

Summary

This RFC proposes allowing macros to be invoked in method position, with an associated extension to macro_rules! to allow such macros to be defined.

Motivation

Idiomatic Rust has evolved such that a tremendous emphasis is placed on methods. Operations can only be overloaded with different behaviours based on type by encoding them as methods of a trait. There is also the increasing number of extension traits, the most prominent example being IteratorExt.

However, there is one particular element of the language that steadfastly does not support method syntax: macro invocation. This becomes an issue when trying to deal with handling errors. For example:

try!(try!(some_thing.with(Several)).extension()).methods()

This code is hard to read because it requires the reader to skip the two try! invocations, read some_thing, with(Several), go back to the innermost try!, back over some_thing and with(Several), read extension, then back to the first try!, then all the way back to methods(). This destroys the readability of "pipeline" style code, which Rust increasingly promotes (via its design) the use of.

Contrast this to the same code, if try! were callable in method position:

some_thing.with(Several).try!().extension().try!().methods()

Although two characters longer, the code is significantly more readable, since it can be read entirely in a single direction.

Given that the general direction of Rust is to de-emphasise panics in favour of returning failure values, making such code easier to read and write is important.

It should also be noted that this change will reduce the immediate need for the previously proposed suffix ! syntax. Although much more verbose, method-position macro invocations would nevertheless reduce rightward drift, and keep code linear.

Detailed design

The first change would be to allow macros to be written in method position. To keep the changes needed minimal, we propose only non-ident macro invocations. The grammar would change to the following:

method_call_expr : expr '.' method_invoke ;
method_invoke : ident paren_expr_list
              | ident '!' '(' tt * ')'
              | ident '!' '[' tt * ']' ;

Note: We only allow the use of parentheses and brackets in this syntax under the assumption that RFC ??? will be accepted; since method position is more "expression-y" than not, braces would not fit here.

There are two potential ways that this could actually be handled. Here, we detail a potentially difficult-to-implement preferred method, and a simpler fallback.

True Method Macros

This approach would involve making method macros a distinct entity from the existing "function-like" macro invocation syntax. We propose doing this by changing standard macros to resemble methods in traits: all macro expansion implementations gain a new invoke_self: Option<&[TT]> parameter. All existing macro invocations would give an invoke_self of None.

Method-position invocations, however, would have the entire "subject" expression provided as Some(tts).

TODO: Examples.

To accomodate these, macro_rules! would be modified such that each rule arm has an additional, optional construct. As an example, the definition of try! could be modified to support method position like so:

macro_rules! try {
    ($expr:expr) => (...);
    $self.() => (try!($self));
}

The rule would only be permitted to use $self as the subject of the method invocation, much as with actual methods.

Desugared Method Macros

This fallback proposal involves simply rewriting all macro invocations of the form $subject.macro_name!($args) into something else such as macro_name!($subject, $arguments) (also for brackets).

This should provide an easy implementation. However, it also makes it impossible to distinguish between method-position- and prefix-position invocations. For example, it would allow the following:

"{}".println!(42)

But not the following, due to the presence of the trailing comma:

TODO: check this is actually correct.

"Hello, World!".println!()
returns_result.try!()

The comma could be removed, but then the first example with println! wouldn't work.

Aside: D has a similar problem in that any function of zero arity can be called without parentheses, and any function with arity one can be called as a "setter". This led to enough confusion that a @property keyword was introduced specifically to disambiguate how a function was supposed to be used.

In either case, no matter what rewrite rule is used, existing macros will likely have to be updated to use it anyway. It seems preferable to give macro authors the ability to tailor their macros based on how it is invoked, even to only allow one form over the other.

Drawbacks

This represents additional complexity, and does not enable any code that was not already possible in a different form. It is purely a convenience.

Alternatives

Do nothing.

Unresolved questions

If the fallback is used, what rewrite rule should be used?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment