Why does macro usage require ! at the end?

Why is it not writeln() but writeln!() when I invoke a macro? Of course, as a user of a library I might be interested in to know that a function being invoked is implemented as macro. But if we follow this 'explicit visibility' principle we might want to have special names for other things, like async code, unsafe code, implicit conversion code or other..

It's mostly for the user's benefit. A function can only ever return a value; a macro can expand to (nearly) arbitrary code, and it's worth calling the user's attention to that.

For example, the try!(expr) macro expands to something like

match (expr) {
  Ok(v) => v,
  Err(e) => return Err(e.into())
}

Anywhere try! appears can cause the function to return if expr evaluates to a Result::Err value. Macros can also declare variables, types, and functions, and do all sorts of other interesting things that functions can't do. The try! macro isn't the only example of this, and in fact you'd usually use ? rather than the try! macro these days, but the pattern holds for macros like async! and await!, and numerous others.

3 Likes

I think the ! is here to make the Rust grammar independant of the symbol resolution.

As you may know, the argument of a macro (the body you write when you invoke it) is free-form. It does not follow a fixed grammar, but instead the definition of the macro itself defines what syntax is allowed. So, parsing the argument of a macro requires to resolve the name of the macro, in order to know its syntax. This is not good at all! Resolving symbols is a step of the compilation that should come way later than the initial grammar parsing of the source file.

So, what we need is a way to contain the macro invocations. Without looking to the meaning of symbols, we need to know that some parts of the code are macros invocations, and we need to know where they end with only the knowledge of the source file we are compiling. In Rust, this is done by marking macro invocations with !, and by requiring the parenthesis and brackets in the body of the macro to be balanced.

15 Likes

@olivren gave a great answer! I'd like to add that one way to have macro calls which syntactically look like method calls is by restricting syntax inside the macro to valid expressions. I believe this is the approach used by Scala.

2 Likes

to make the syntax unambiguous. C macros work as you describe, and are so dangerous and awkward for compilation (e.g. figuring out dependancies, producing unexpected effects..) because of it - can #define anything. Macros are processed at a different step in compilation, the ! marks it.

I do agree that they do sometimes stick out like a sore thumb - I would like to see the use-cases of macros reduced a little (by beefing up what the native function call syntax can do, e.g. n-ary overload, keyword args, duck-typed single line functions that pretty much behave like macros , etc) ... keeping them for the use cases they excel at (like rolling UI bindings for data structures, asserts/debug etc etc etc... things that functions definitely can't do ) .

(I find mentally theres an overlap between some use cases of macros , 'inline functions', and templates/generics etc .. e.g. there was a time when those simple functions would be written as macros in C, then we'd change them to inline templated functions in c++.. )

1 Like