I hope this isn't too much of a derail. I'm not proficient in C, and am translating a C module to Rust. My biggest complaint is there's so much DRY! Functions, variables etc are all prefixed with the module names, presumably due to lack of namespaces. The `.c` vs `.h` dichotomy is also DRY. The subset language that uses `#` is also a bit of a mess.
You're using DRY oddly, but I presume you mean "there is so much duplication" (which is the opposite of DRY)?
For the .c/.h part, it's because of use (or intended use). Header files are meant to be specifications (though a lot of implementation ends up in them, annoyingly). It actually is an example of DRY thanks to C's need to forward declare things. You can't call a function the compiler doesn't know about, for instance, so you declare (not define) it in the header if it's defined in an external module. If you didn't have the headers, you'd need to copy/paste or handwrite the declarations everywhere.
You can call a function the compiler doesn't know about, but god help you if there happens to be an identical symbol in the table; it will link to literally anything, including an integer. This should pretty much always be turned off by setting implicit functions to be errors.
Also, this explains all the prefixing that OP was complaining about lol, or at least that's one reason. I saw a particularly bad bug, due to poor naming decisions. Less is not always more, even if it annoys you.
Why do you think there needs to be a declaration beyond the definition? The definition is enough information for the compiler, and having both is a DRY violation.
Header files are a historical legacy from when C was developed. Because machines were small compilers/languages were designed to do single pass compilation on modules which were then linked.
Friends of mine who are actual CS majors say with C it's not fixable without changes to the language. With Java or C# the compiler can do a pass and extract the object definitions. But because of ambiguous syntax and majorly because of the preprocessor it's not possible with C as it stands.
You need the declarations because the compiler requires it, it's not something that I just thought up. If you don't, then you get two different declarations for bar in the following. The one you intended (char bar(int)) and the one you didn't (int bar()):
int foo(int n) {
char c = bar(n);
...
}
char bar(int n) { ... }
The definition is not enough information for the compiler in a case like this, which is also essentially the case for any externally defined function. The compiler doesn't know anything about those unless its declaration is provided, which is what's in the header files.
Did I say there were two definitions? I said there are two declarations. Try using a function with a return type other than int before it has been defined or declared in C and see what happens when you compile it.
The function is declared/defined/described/written once, and used once. There is no ambiguity.
I don't understand your point about using a function that hasn't been declared. Of course it won't work!
I understand your point about using header files as interfaces to third-party code, but there are ways of doing that that don't involve duplicating all functions, structs etc.
The function is used once, and because it occurs before the actual definition (and there was no forward declaration) an implicit declaration occurs (the erroneous int bar()). When the function definition is later reached by the compiler, its signature doesn't match the implicit declaration's signature, causing the problem. If the function signature matches the implicit declaration, then there is no problem:
int main(int argc, char* argv[]) {
printf("%d\n", foo(10));
return 0;
}
int foo(int n) {
return n - 1;
}
Will work just fine, giving you only a warning but will print out "9" as expected.
> an implicit declaration occurs (the erroneous int bar()
I still don't understand; why does the implicit declaration assume int bar() is the signature when bar is being passed an int and returning a value to a char?
You don't need type inference to handle this correctly, you just need to wait until you've parsed the rest of the file and seen the signatures of all functions.