back

by uecker·1y ago·view on hn ↗
Safety in C improves dramatically with good tooling and coding practice and you can slowly move to safer C without much pain. People who really do not care will also create problems with Rust.
1 comments
We've discussed this before. The issue is the same that it's always been. A single mistake anywhere in your codebase means that all language invariants are no longer upheld. No tooling can statically guarantee defined behavior, only catch some of the easier cases. I work in a codebase where we do all of that high powered static analysis and I still regularly find issues with simple tools like ubsan (which isn't remotely exhaustive).

Any instance of undefined behavior in safe rust is a language or compiler bug. The only things you really need to check are unsafe blocks, which are rare enough to individually review line by line on a regular basis.

I see Rust code with quite a lof of unsafe and sometimes it has bugs, sometimes triggered from preconditions broken outside of the unsafe block. So no, I think it is an oversimplification to pretend that "only a couple of lines need to be reviewed". And then there other safety issues than memory safety.

But I think a safe C that has similar guarantees as Rust is quite possible and we can gradually move there.

I only mentioned undefined behavior. Memory safety is a subset of those issues, but rust's actual promise is eliminating all UB in safe code.

In almost all cases for preconditions, that's solvable by better practices that could be identified in a review. It's the same underlying problem as C, but dramatically more manageable.

And of course, any C is liable to be misused in C++ where the committee's current position is that the problem is unsolvable.

It is the same problem in C and more manageable. I just doubt the "dramatically". And C problems also become more manageable with better tools and more focus on safety in the language evolution.
I completely agree that C and C++ become more manageable with modern tooling. The problem is that better doesn't fundamentally change the situation. How UB is defined in the standards means semantics are an all or nothing situation. Either there's no UB and you have the semantics specified by the standard, or you have UB and no semantics (past the point of UB in C23 thanks to your proposal). We've had a solid 30 years of history demonstrating that exhaustive static detection is impossible and every nontrivial program has UB of some kind.

You can't fix this with minor changes in the language or improved tooling. You need a complete overhaul of how the standards and implementations deal with UB.

But Rust shows that static detection is possible if you add constraints to the language, and this is exactly what one can do it C too. Rust was inspired by a C dialect after all. The situation with UB is not actually as bad as it sounds (I analyzed all UB in the core language).
Ignoring memory safety where rust's solution is more obvious, the lack of UB in Rust is mainly the result of consciously deciding to eliminate it. Signed overflow is implementation defined for example. The parsing UB that takes up half of annex J is eliminated by simply expecting that implementations write proper parsers and having a better macro system. Invalid enum values were solved by a stronger type system.

If there were actually an appetite to eliminate it, I suspect we could probably eliminate 70% of J.2 with a few straightforward changes. Many of these aren't completely amenable to static analysis, like signed/shift bounds, but they're workable at the language level. The members of the C++ committee I've talked to were skeptical about the actual chances here though.

The rest of annex J is at least partially addressed with existing efforts like checked C and Coral ("constraints"), so at least there's a path forward with tooling.

You can always wrap C into a higher level language that makes it safe, but why the hassle? That new language might as well be Rust,which is built up from the bottem with safety in mind.
Rust has quite a few problems which why I would not want to use it for my projects: High complexity, long compile times, not stable, not standard, supply chain issues, etc.
I genuinely don’t think we can move there at all. There are significant cultural issues: as soon as a safe version with bounds checking is 2% slower, a significant chunk of programmers will claim they can’t use it. There’s also the glacial pace of tooling changes, so even if a safer C existed today, it’d be a couple decades before it was commonly used.

But frankly, if you’re going through all that trouble, it’s probably worth considering a newer language that fixes those things today. I mentioned Rust as one example, not the end-all solution to all problems. Nim also plays on the same space and it’s quite nice. The broader point is that I believe it’d be easier to start writing new code in a different language that can easily call existing C code.

I do not think newer languages are the solution for two reasons:

First, all those new languages also have various downsides, and trade-offs, which may make them a less than ideal choice for many applications. Second, there are an incredible amount of existing C code. We will not get rid of C in the next at least 50 years even if we wanted to (not that I want to). Incrementally improving that code is far more useful than adding other languages into the mix.