The clean solution is to do what clang does and generate informative error messages. In the case of macros, by showing the macro expansion history. For example, the following (using __STDC__ instead of linux for portability):
foo.c:2:7: error: expected identifier or '('
int __STDC__ = 1;
^
<built-in>:159:18: note: expanded from here
#define __STDC__ 1
Note that there can be other reasons to have macros that are syntactically different from functions and variables, but this isn't one of them. > If the compiler silently predefined an undocumented
> variable, type, or function called "linux" or "unix",
> that too can be a problem.
While we're on the topic of Rust, the compiler actually used to do this! In the early days, the identifiers "error", "warn", "info", and "debug" were all magical, undocumented constants for controlling logging levels. As you might expect, this caused problems. :P So don't think that Patrick isn't keenly aware of the perils of including non-keyword identifiers by default.As for clang, their solution is the best they can do given that they don't have the authority to make the call over whether to syntactically disambiguate macros. It's definitely a debate to be had in the case of a language designed from scratch, and the Rust devs decided to err on the side of explicitness (you can contrast e.g. Elixir, which has the same sort of structural macros but chose not to distinguish their invocation syntax).
Metaprogramming is "dangerous" because the meta constructs don't (can't!) act like what they look liks. Metaprogramming is "great" because the meta constructs look like simple programming but have surprisingly deep behavior.
As I see it all you did was add a "!" to the confusion, honestly. People likely to get tripped up by e.g. the "address of macro" problem above are going to be no less confused by the extra syntax IMHO.
For clarity I just want to note here that Rust macros are structural (like Scheme) rather than textual (like C).
But the more abstract point is that macros allow you to provide abstractions that do look just like an identifier (or function call, etc...) and do act like identifiers in the context where they're used. And that this is a good thing, because that kind of syntactic flexibility is useful in defining domain-specific languages that make the expression of hard problems clearer.
Adding a bunch of "!" to those DSLs isn't helping anything.
Basically, we have a macro that was abused here. Your solutions it to make it harder to abuse macros, mine is to accept the occasional abuse (with a "don't do that" in most cases, those here it's a backwards-compatibility problem) in exchange for more clarity on the hard stuff.
I was just quibbling with the idea that putting a magic "!" on a macro somehow makes it a better design choice. I don't think it does, either as a macro or as a safety mechanism.
Oh well, perfection is no of this world.
All this would happen if gc was in the std library anyway though.
1) We have an attribute that you can stick in a module that will throw an error at compile time if a managed pointer gets used anywhere in that module, including in its dependencies. (Note: this is still experimental, and I'm not sure if it actually works yet.)
2) It's possible to write Rust entirely without a runtime, which means your result binary just plain won't contain any of the machinery for GC, tasks, or TLS (task-local storage).
This second approach does have consequences. Bad: random bits of the standard lib won't be usable, since our preferred means of error handling uses TLS (and some functional/persistent datastructures will use GC to handle cyclical pointers, since that's our only internally acceptable use case for GC). We have plans to look at dividing the stdlib into "usage profiles" to allow users to selectively disable certain runtime features while still remaining aware of exactly which pieces of the stdlib are still usable.
But the good consequences of disabling the runtime are that if your code exposes a C ABI, then you can write a library that can be called from any other language with a C FFI (i.e. basically every language, ever). This is actually one of my favorite things about Rust.