A controversial topic among programming language designers is preprocessor macros such as #define directives in C. They can cause problems if used incorrectly.

#define FIVE 4+1
const int TEN = FIVE*2;
printf("%d\n", TEN); // 6! OOF!

Many/most modern languages solve problems of the like by leaving out macros entirely. However, what would be a better way, from a language design perspective, to include macros, while being harder to misuse?


2 Answers 2


Preprocessor macros can cause problems in two common ways:

  • By expanding at the token level rather than the AST level, the precedence of operators may not be respected. (This problem is illustrated by the OP.)
  • Names declared by macros may conflict with other names used at the macro's "call-site", either shadowing or overwriting them in an unwanted way; conversely, names used in a macro may be inadvertently shadowed at the "call-site". (This is called the hygiene problem.)

A good example of how to mitigate these issues is Rust's macros:

  • Rust's macros operate on "token trees", an intermediate structure between the raw token stream and the AST. A token tree is a sequence of tokens and/or other token trees, and the lexer builds token trees automatically using the standard delimiters (-), [-] and {-}. Then when a macro is expanded, the result is a token tree, and therefore its tree structure can't be disrespected by the parser.
  • Rust's macros may only use names which are either in scope of the macro declaration, or which occur in the macro's parameters. In the former case, those names are resolved lexically from the macro declaration site rather than where the macro is used; in the latter case, they are resolved lexically in the usual way after the macro has been expanded into code which contains those names, so that they will refer to the same things as they referred to in the macro's arguments.

The drawback of mitigating these issues is that it prevents macros from being used intentionally for these features. Modern languages tend to prefer protecting users against common mistakes, but there are still lots of programmers out there who like C's philosophy ─ they want sharp tools without safety features, and have it be their own fault if they hurt themselves. So make this choice based on who your language is aimed at.


I mean... define "used incorrectly" and "misused". There isn't really "correct" or "incorrect". There's "it does what the user wanted because they wrote it to do that" and "it doesn't do what the user wanted because they wrote it to not do what they wanted".

Part of the whole appeal of features like this is their "powerfulness".

"Misused" has a sense of the word for "abuse"- like abusing the feature. But these features are kind of designed for you to do things that some might call abusing the language.

I would argue that something you do to change the behaviour of what's written in your question post's example (Ex. making the macro implicitly parenthesizing the replacement) for C would be arbitrarily limiting the power of the feature in the name of a specific use-case. And to some language designers, that would be unacceptable. I think it almost certainly would be for the designers of C.

If your goal is to mitigate incorrect usage...

I suggest an approach that's a little outside the box of what you're probably thinking of: Better user documentation, and better user teaching materials. Educate users about how a feature works, and demonstrate various behaviours through minimal examples. In the "sharp knives" discourse, this is an argument for teaching the users to wield the sharp knives instead of hiding or dulling the knives. That also solves what is the root of the problem you present in your example: The code didn't do what they wanted- presumably because they didn't have an awareness of (or a sharp enough awareness of) how the feature really worked.


You must log in to answer this question.

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