Comment by cratermoon

10 months ago

I'm curious now. What are the backwards compatibility guarantees for C?

As long as you compile with the version specified (e.g., `-std=c11`) I think backwards compatibility should be 100%. I've been able to compile codebases that are decades old with modern compilers with this.

  • In practice, C has a couple of significant pitfalls that I've read about.

    First is if you compile with `-Werror -Wall` or similar; new compiler diagnostics can result in a build failing. That's easy enough to work around.

    Second, nearly any decent-sized C program has undefined behavior, and new compilers may change their handling of undefined behavior. (E.g., they may add new optimizations that detect and exploit undefined behavior that was previously benign.) See, e.g., this post by cryptologist Daniel J. Bernstein: https://groups.google.com/g/boring-crypto/c/48qa1kWignU/m/o8...

    • Why not entirely wrong, the UB issues are bit exaggerated in my opinion. My C code from 20 years ago still works fine even when using modern compilers. In any case, our plan is to remove most of UB and there is quite good progress. Complaining that your build fails with -Werror seems a bit weird. If you do not want it, why explicitly request this with -Werror?

      2 replies →

    • The warning argument is silly. It just means that your code is not up to par with the modern standards. -Wall is a moving goalpost and it's getting new warnings added with every release of a TC because TC developers are trying to make your code more secure.

      1 reply →

  • Not even close to 100%, the reason that it feels like every major C codebase in industry is pinned to some ancient compiler version is because upgrading to a new toolchain is fraught. The fact that most Rust users are successfully tracking relatively recent versions of the toolchain is a testament to how stable Rust actually is in practice (an upgrade might take you a few minutes per million lines of code).

  • gets() was straight-up removed in C11.

    Every language has breaking changes. The question is the frequency, not if it happens at all.

    The C and C++ folks try very hard to minimize breakage, and so do the Rust folks. Rust is far closer to those two than other languages. I'm not willing to say that it's the same, because I do not know how to quantify it.

  • C (and C++) code breaks all the time between toolchain versions (I say "toolchain" to include compiler, assembler, linker, libc, etc.). Some common concerns are: headers that include other headers, internal-but-public-looking names, macros that don't work the way people think they do, unusual function-argument combinations, ...

    Decades-old codebases tend to work because the toolchain explicitly hard-codes support for the ways they make assumptions not provided by any standard.

For the purposes of linux kernel, there's essentially a custom superset of C that is defined as "right" for linux kernel, and there are maintainers responsible for maintaining it.

While GCC with few basic flag will, in general, produce binary that cooperates with kernel, kbuild does load all those flags for a reason.

  • > For the purposes of linux kernel, there's essentially a custom superset of C that is defined as "right" for linux kernel

    Superset? Or subset? I'd have guessed the latter.

    • Superset. ANSI/ISO C is not a good language to write a kernel in, because the standards are way more limiting than some people would think - and leaves a lot to implementation.

      So it's a superset in terms of what's defined

The backwards compatibility guarantee for C is "C99 compilers can compile C99 code". If they can't, that's a compiler bug. Same for other C standards.

Since Rust doesn't have a standard, the guarantee is "whatever the current version of the compiler can compile". To check if they broke anything they compile everything on crates.io (called a crater run).

But if you check results of crater runs, almost every release some crates that compiled in the previous version stop compiling in the new version. But as long as the number of such breakages it not too large, they say "nothing is broken" and push the release.

  • Can you provide an example for the broken-crater claim? As far as I'm aware, Rust folks don't break compatibility that easily, and the one time that happened recently (an old version of the `time` crate getting broken by a compiler update), there were a lot of foul words thrown around and the maintainers learned their lesson. Are you sure you aren't talking about crates triggering UB or crates with unreliable tests that were broken anyway?

  • What do you mean? Rust 1.0 can compile Rust 1.0. Rust 1.1 can compile Rust 1.1.

    • C makes a distinction between the language version and the compiler version. Rust does not. That's the problem people are discussing here.

    • C99 isn't a compiler version. It's a standard. Many versions of GCC, Clang and other compilers can compile C99 code. If you update your compiler from gcc 14.1 to gcc 14.2, both versions can still compile standard code.

      1 reply →

    • Right, which is basically the opposite of what backwards incompatibility means. Imagine if GCC 14.2.0 was only guaranteed to be able to compile "C 14.2.0".