31
$\begingroup$

Why did the designers of the Rust programming language require that macro names have to end with an exclamation point?

I observe that both Common Lisp and Emacs Lisp language variants do not appear, at least to this language abuser, to require a special suffix on the names of non-hygienic macros (e.g., defmacro). Those languages do not, at least to this malpracticing developer, appear to exhibit technical problems in their approach to macro usage, that would require any punctuation suffix. This exclamation point would appear to my eye to separate function calls from a macro calls, causing them to appear to be treated and resolved into distinct, separate "conceptual spaces", "namespaces", or similar notion. So, that consideration is what drives my curiosity to ask this question here.

I am not stating that this design choice is/was incorrect/wrong. Nor am I suggesting it should be changed, or even could be changed. I am just curious.


TL;DR Backstory

See the StackOverflow question for my semi-failed attempt at getting an answer from what I would presume are Rust users.

That attempt partially failed because all comments and the answer there (as of 2023-10-28; more answers may appear over time) appear to be guesswork or speculation, but one comment pointed at this site. I'm hoping someone can point us to the answer to some definitive reference provided by the originators/authors/designers of the Rust language to answer this question.

$\endgroup$
5
  • 3
    $\begingroup$ Is this asking the first question about the reason the Rust designers required marking macro invocations with an exclamation point, or the second question about scientific evidence that distinguishing macros is necessary? $\endgroup$
    – Michael Homer
    Oct 28, 2023 at 21:32
  • 1
    $\begingroup$ The current answer seems to address the first & title question pretty definitively - I suggest the question be edited down to just that one, and the other part be carved off to a separate question if it’s of interest. $\endgroup$
    – Michael Homer
    Oct 28, 2023 at 23:50
  • 1
    $\begingroup$ The practice of using ! foir macros in lisp is a convention (to make macro invokations obvious to the reader) and not a requirement. But most consider it a "good" convention and would consider writing a macro without ! to be bad practice and needless obfuscation. $\endgroup$
    – Chris Dodd
    Oct 29, 2023 at 4:11
  • $\begingroup$ I've seen the same naming convention somewhere (Scheme, I think) for functions that have side-effects. When I've glanced at Rust, I'd just assumed this was the same meaning; since I use C and C++ which use all-caps for macros, it never occurred to me what I was actually seeing. $\endgroup$ Oct 30, 2023 at 15:55
  • 1
    $\begingroup$ @TobySpeight That came up in comments below as well - it is a convention in at least Scheme, Ruby, and Julia (probably due to their influence on each other). Ultimately, it's a similar reasoning, although a different specific case: "Warning! This invocation is a bit different from the ones around it!" $\endgroup$
    – IMSoP
    Oct 31, 2023 at 16:45

3 Answers 3

50
$\begingroup$

It seems that the earliest version of macros in Rust, referred to as "language extensions", used #foo instead of foo! as the invocation syntax. The two syntaxes seem to have coexisted briefly, until the transition was completed in Rust 0.5 at the end of 2012.

While I haven't tracked down the discussions leading to either syntax, I did find a related discussion from summer 2012 (if I'm following the timeline correctly, this was possibly the start of a discussion which was continued elsewhere and led to the foo! syntax). Some of the comments touched on removing the # marker, leading to this reply from Graydon Hoare, one of the language's main designers:

The issue of # is not about making parsing simpler, it's making macros not look first class. Intentionally. Because all bets are off when it comes to interpreting their contents. #fmt doesn't even construct a string literal at runtime. #log is going to lazily-evaluate its arguments. #rx is going to compile a regular expression matcher. These are quite non-obvious and not something that will jump out at the reader if they just glance over the expressions making up the arguments. The # is a clue to the reader not to expect normal evaluation rules inside. Making things look like normal-evaluation-rules is, in my mind, an anti-feature. I realize the team has differing opinions about this, but my preference has been pretty consistent from the start.

This seems to be pretty clear evidence that the primary reason for the distinct syntax is to help human readers spot macro invocations, with the aid it gives to parsers a secondary concern.

In other words, when you write:

This exclamation point would appear to my eye to separate function calls from a macro calls

You are seeing exactly what Hoare hoped you would see: "this isn't a function call, it's something different, that won't obey the normal rules".

$\endgroup$
7
  • 4
    $\begingroup$ Similarly, Julia appends (by convention) exclamation marks to the ends of functions that modify their arguments to call attention to them. $\endgroup$
    – apropos
    Oct 29, 2023 at 3:09
  • 7
    $\begingroup$ even more similarly, Julia makes calls to macros start with a @ $\endgroup$ Oct 29, 2023 at 5:20
  • 7
    $\begingroup$ @apropos Julia probably inherited that use of ! from Ruby, which also uses names ending ? as a convention for returning a Boolean. That in turn probably goes back to the Lisp convention of using -p for "predicate". $\endgroup$
    – IMSoP
    Oct 29, 2023 at 9:32
  • $\begingroup$ @IMSoP Thank you. Here is the direct-to-the-comment link for a related discussion from summer 2012. $\endgroup$
    – bgoodr
    Oct 29, 2023 at 15:19
  • 2
    $\begingroup$ That seems like a reasonable answer because to get deeper into it would require direct responses "from Graydon Hoare, one of the language's main designers" who it appears on the surface to have the final vote on the eventual requirement for the exclamation mark ornamentation. Thanks, IMSoP! $\endgroup$
    – bgoodr
    Oct 29, 2023 at 15:52
10
$\begingroup$

IMSoP's answer appears accurate and provides research, I recommend reading it. To summarize, it suggests that the ! used to invoke a macro is primarily there to improve readability for humans. What I want to discuss is my thoughts on why we might want that in Rust, and how it aligns with Rust's ideology.

Let's consider that Rust is an opinionated language. Its designers and developers formed an opinion about how a systems-level language could operate to drastically improve memory safety without compromising speed (among other reasons). This is reflected in Rust's compiler, which will not easily allow you to perform operations that don't comply with the intended design of Rust. Of course, this comes with the cost of a steep learning curve for those used to more traditional languages, but Rust's opinion is that the tradeoff is worth it.

One place we see Rust's opinionated design frequently reflected is in its syntax. Specifically, we see that Rust wants us to be highly explicit when writing code that could be misinterpreted or result in unexpected or unclear behavior. For example, the Rust compiler forces us to declare variables as mutable with the mut keyword, because in Rust's opinion, not doing so can lead to behavior that's considered unexpected or dangerous.

Similarly, we are required to use an ! when invoking a macro because the behavior/usage of macros is not always clearly defined. Macros are incredibly flexible and have very few rules of engagement, unlike functions which have very specific rules for engagement (e.g., they must specify arguments, argument types, and return type). Thus, by requiring an ! to invoke a macro, we ensure that the user is explicitly invoking the macro, which aligns well with Rust's opinionated design.

You may still wonder, why don't other languages do this then? The reason is because most other languages are unopinionated. Consider a language like C for example (I'm not familiar with its macros/preprocessor, just using it as a general example). As long as you use valid syntax that the compiler can understand, it doesn't care what else you do. It doesn't care if you pass around references to mutable data or invalid data, and it doesn't care if you accidentally forget to unallocate memory that you used, etc. Instead, the C compiler assumes you know what you're doing and therefore transfers the responsibility to you as the developer. It trusts you to do the right thing without requiring any reassurance.

Rust is the polar opposite, where it doesn't trust you and doesn't let you do something that's dangerous unless you are very explicit about it. This is why it makes sense to have a required symbol to invoke macros. The Rust compiler only trusts you if you first reassure it that you know what you're doing at every step of the way.

$\endgroup$
6
  • 2
    $\begingroup$ It certainly can be difficult to read Emacs Lisp code when you don't know which things are macros. $\endgroup$
    – SamB
    Oct 30, 2023 at 21:43
  • 9
    $\begingroup$ I'm not sure it is really accurate to say that C is an unopinionated language. I think C is very opinionated ─ but C's opinion is "tools should be as sharp as possible, and usefulness is always preferable to safety", and that opinion doesn't entail that macro invocations be clearly distinct from function calls. $\endgroup$
    – kaya3
    Oct 30, 2023 at 22:04
  • 1
    $\begingroup$ @SamB "These are quite non-obvious and not something that will jump out at the reader if they just glance over the expressions making up the arguments. " from Graydon Hoare seems to align with that sentiment. $\endgroup$
    – bgoodr
    Oct 31, 2023 at 0:32
  • 1
    $\begingroup$ @kaya3 I agree with your sentiment, and should explain what I meant. By "opinionated," I meant in the way that the language enforces its opinions. Similar to an opinionated framework, where it becomes much less useful if not used how intended. That's not to say languages like C don't have opinions or conventions, but rather that you can basically ignore them if you want. Conversely, ignoring Rust's opinions makes the language highly difficult to use, if not impossible in some cases (unless everything is in an unsafe block). There's probably better terminology and a better analogy I could use! $\endgroup$
    – Shayes
    Oct 31, 2023 at 1:25
  • 1
    $\begingroup$ It may very well be that Rust thinks you (programmer) are a fumbling fool and "it doesn't trust you". Another point of view is that having more constraints and giving the compiler more information lets it make many more checks and ensure much more of what otherwise you'd have to do yourself (because you're a pro). In other words, let the compiler handle the details, and focus on design, algorithm choice, and so on. $\endgroup$
    – Pablo H
    Nov 1, 2023 at 16:39
1
$\begingroup$

Macros and functions are fundamentally different.

  1. The evaluation of a function happens at run-time. The evaluation of a macro-call happens at compile-time.
  2. A macro is no code. Instead it is a code generator. Therefor people call the evaluation of macros expansion to distinguish it from the actual evaluation.

The exclamation mark is Rust's way to distinguish them.

It may be useful to read a macro tutorial for Scheme, because Scheme has no special indicator for macros. By looking at some Scheme code you can not distinguish macros from procedures. You have to know, which expressions are macros. The Scheme standard lists for every single form if it is syntax (a macro) or not. Many people think it is nice that they look the same. I do not agree, because it is the root of the confusion the mentioned tutorial tries to address. In Rust there is no confusion, because of the exclamation mark.

$\endgroup$

This site is temporarily in read-only mode and not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .