back

by uecker·1y ago·view on hn ↗
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.

3 comments
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.

The things you mention are already ongoing. We eliminated 15 items in J.2 in last meeting and will remove more in the next. Not that I agree that the UB is really the that much of a problem, only very specific cases of UB actually cause real safety problems.
In my domain (safety critical), I'm interested in taking a piece of code I didn't necessarily write and making statements about the state of the system at various points in the execution. It doesn't matter whether the UB that exists doesn't result in observable misbehavior of the system as it exists today, I also need to ensure that the system will continue behaving in 10-50 years with a different compiler on a different architecture that may not even be designed yet.

Currently, all of that work has to come with a little asterisk saying "except for undefined behavior". I can't constrain where UB might be in the system, or how it might violate the safety model. In short, if I do my job properly I shouldn't assume C/C++ semantics at all. Obviously that's not reasonable, so everyone goes around pretending this isn't an issue to their safety models and their regulatory filings and so on.

Obviously I think that's wrong, but there haven't been ways to address it in C and C++. It's possible in Rust.

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.