Why does the C preprocessor interpret the word “linux” as the constant “1”?
stackoverflow.com
stackoverflow.com
When I see a lowercase symbol used as a variable, I expect it's going to be some actual global variable. When I see a lower case symbol used like a function I expect it's an actual function. And then comes the day I want to take the address of the "function" and it fails. Or worse, I make a syntax error somewhere and because of the macro expansion it gives me the most perplexing error message in the world and it takes me 5 minutes and digging through 4 levels of include files to realize I forgot a colon or something.
Not to mention the mess if the macro is poorly implemented and does things like using a parameter several times which causes hard to catch undefined behaviours in the application. Nothing worse than having a seemingly harmless code like "foo(a++)" turn into the bug of doom because you didn't know foo was a macro. Please don't do that.
On the other end when I get a weird behaviour/error message around a capitalized symbol I automatically think that it must be a macro and go dig for the definition.
On the whole, this has been very successful - about half of D programmers have a C/C++ background and aren't shy about being vocal if there's something they really need.
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.
(1) I had to port a game engine to solaris a few years ago. In that engine, if you looked up, you'd see a star emitting light. Guess what 3-letter, all-lowercase identifier was conflicting.
(2) Xt (only useful, really, if you're doing Motif work) had a macro or two that looked exactly like a function call, and had a very natural use case for incrementing an index you feed as an argument. If you knew not to do it, no problem. Otherwise, that was a mean bug to track down.
sel? The suspense is killing me! :)
sun
If I can't grep the header files/source code for a function call I will want to hurt you badly. Especially if a function call is hidden behind 6 - 7 different macro indirections!
/me glares over at OpenSSL...
Unfortunately, the standard preprocessor output doesn't provide a convenient way to do this without a comment format. I guess you could use a do-nothing #pragma.
Eventually, I'm sure somebody will spin up some extension to clang to show you the complete evolution of a block of code and what other code contributed to that evolution.
For instance running it on the following code in the middle of a C source file:
#define BOGO_MAX(a, b) a > b ? a : b
int test()
{
return BOGO_MAX(3 + 4, 5);
}
Creates a new buffer containing: int test()
{
return 3 + 4 > 5 ? 3 + 4: 5;
}That being said if I'm not sure if some code is being compiled I just add an "#error foo" and rebuild. Or even simpler I just type in some garbage to trigger a compilation error.
PS: That's one of the few reddit customs I really like "link for the lazy" :)
Edit: Or /.*BSD/. :-(
"That's why [insert other language] has [insert feature]. This avoids some of the edge cases in [insert popular non-C language] along the lines of [insert unused obscure language]. Early benchmarks suggest it's almost as fast as C!"
(No relation to Ken Thompson, if that's what you were thinking.)