Hacker News new | past | comments | ask | show | jobs | submit login

When people talk about Rust's advantage in safety, is it just the compile-time checks about Ownership? Could not the C compiler be given the same thing, with a flag like "strict mode"?

Sorry if my question is really stupid. My experience has been with scripting languages, not systems programming. But I read the chapter in the Rust Book about Ownership, and I think I get it. In a sense, is not the compiler simply inserting function calls to free() in the right places (and warning you if it can't)? Couldn't this be added to the C compiler? In fact I'm not sure what the difference is from a linter.

The article has the phrase "C/C++ Can’t Be Fixed", so it anticipates my question. But its answer is that programmers either won't remember or won't bother to set their C to "strict mode", run the linter, or whatever, because "static analysis comes with too much overhead: It needs to be wired into the build system. . . . If it’s not on by default it won’t it won't help." But if this is the only argument, it seems weak. I agree that it is more effort, but isn't it much more effort to switch out your entire language and ecosystem?




> is it just the compile-time checks about Ownership?

Sometimes. There's a lot more to safety than memory safety, but that's the easiest to define.

Other aspects might be safety against null values, safety against poor error handling, etc. Those are much harder to define.

> Could not the C compiler be given the same thing, with a flag like "strict mode"?

Not really, no. There's a lot of research into proving C code safe, and automation of it, and it's not really feasible for general C codebases. There are tons of static analysis tools that try to get part of the way there, but they can be slow, miss things, and have many false positives.

It would require, at minimum, some seriously ugly annotations. There are papers that demonstrate such annotation based approaches with C. One that I read ended up looking quite a lot like Rust, at which point, you wonder why you wouldn't just write rust.

Not a stupid question at all, by the way.

> Couldn't this be added to the C compiler?

Rust is able to insert those frees because it has lots of information about memory usage at compile time. C doesn't, therefor C can't insert those frees in the general case.

Other issues, cultural ones that is, like "people just disable it" are fair but not the biggest issue imo. But to further emphasize that point there are many runtime mitigation techniques (ASLR, stack cookies), and they get disabled all the time because, and this is the extra sad part, they legitimately find bugs and crash a program that otherwise may have appeared to work, and the fastest "fix" is disabling it.

Re: Effort, totally, yes. But the effort isn't really split. There are tons of people working on safer C/C++ through tools like fuzzing, static analysis, and runtime mitigations.


> you wonder why you wouldn't just write rust

Because it is another language.

If the researchers behind the borrow checker had implemented it on a safe variation of C/C++, then it would have been much easier to push for it in many companies. Something like the Python 2/3 split is better than another completely different language.

Don’t get me wrong, Rust brings improvement in other areas too, which is fine, but when we are talking about many-million-LOCs, you don’t care that much.

I am still waiting for someone to bring lifetime annotations to C and C++. Those I could use right away. Rust, not so much.


But adding rust-like lifetime annotations to a many-million-LOC code base would result in totally bricking the code with 99.99999% certainty.

I tried to port an (Earley) parser from C to Rust. It wasn't even a port, but more of a rewrite with similar function structure. The first part worked well, even though I had to resort to an arena (a special type of allocator that can guarantee lifetimes at the expense of freeing them later than strictly possible). Then I tried to get in shared parse trees. It was a nightmare. It could simply not be done with the same efficiency as in C. I had to resort to using array indices instead of pointers, or use ref counting where it wasn't needed. This had quite some impact on the calling functions.

And that was a few hundred lines of plain, non-tricky C.


If your C/C++ is sane, then your code was already written with lifetimes in mind and enforcing them is not a big deal.

There will be places, no doubt, that you cannot do it, but if you can easily cover 80% of the code, that is way better than nothing and then you can work on redesigning the 20% (perhaps even rewriting it in another language since it has to be changed, for instance to Rust). Same argument as Rust uses for unsafe blocks.

There have been specialized annotations for things like mutexes for a while in several projects which help static analyzers prove things too, so it is not new.


Doesn't unique_ptr in C++ provide that?


unique_ptr allows for use-after-move, so it's not perfect.


The issue is that C/C++ Can’t Be Fixed backwards compatibly. You would need to add lifetime annotations: existing code wouldn't compile. You could make a Rust-style language that is closer to C, but it wouldn't be any to use than Rust.

And at that point why not just use Rust. It doesn't require you to switch out the entire ecosystem, you can happily link Rust and C, and C++ together in one binary. In fact, as an end-user it's sometimes easier to use a popular C library from Rust because someone will have wired it into Cargo (Rust's build system) and it will "just work": you don't have to mess around with working out what kind of build system the library is using and integrate it with whatever your using.


> And at that point why not just use Rust

See my sibling comment. Using Rust implies many more changes than just moving code to an incompatible-but-close version of C and C++ that adds lifetime annotations and unsafe blocks. Compare it with the Python 2/3 split vs. rewriting all your Python 2 code to, say, Ruby.

Then there is the OOB issues too, like using another build system, a single compiler based on LLVM, etc.


Given the degree of changes required in some codebases, I think some teams would rather treat it as a rewrite job.

Even moving large enterprise codebases from python 2 to python 3 is taking so long they had to push that deadline back years. Given the distance you'd have to cross to push an older C++ codebase to something with similar guarantees as Rust, I don't know if any company would voluntarily cross that gap.


> adds lifetime annotations and unsafe blocks

Worth noting that the vast majority of lifetime annotations in Rust are “elided”, i.e. inferred by the compiler.

I’d say that a successful “safe C” compiler would accept any existing C code that never invokes undefined behaviour, without a runtime performance penalty.


> I’d say that a successful “safe C” compiler would accept any existing C code that never invokes undefined behaviour, without a runtime performance penalty.

That's unfortunately what's not possible. The C source code doesn't contain enough information to prove safety (a lot of real world C code depends on invariants that happen to be true at runtime, but aren't provably true at compile time)


Yes, it probably is impossible. On the other hand, C programmers are asked to write code which doesn't invoke undefined behaviour otherwise their code can break in unpredictable ways, so either we just don't have sufficiently good checking algorithms yet or it is impossible for them too.


What you described is just a destructor. C++ already has those. If you think that destructors are all there is to Rust then you are missing the entire point.

Rust has an affine type system [0] and this is something you either have or you don't. There is no way to retrofit it without rewriting all C code in existence.

When you look at linear types it becomes pretty obvious how restrictive they are but this restriction is also what allows them to make guarantees about memory safety. With linear types every variable must be used exactly once. This means that no matter through how many functions you pass a variable eventually you have to call the destructor to destroy the variable.

Rust doesn't have linear types because they are too restrictive. Instead it has affine types where variables are used at most once and if they are not used the destructor is called automatically. However even that is too restrictive. The definition of "use" in Rust is a borrow and the affine type system rules only apply to mutable borrows. You can have an arbitrary amount of read only borrows. The end result is a language where it's not possible or much harder to implement data structures that do not follow these rules such as doubly linked lists. You will have to use completely different data structures in a variant of C that has an affine type system. This will probably result in an affine only ecosystem that is completely disjoint from the rest of C. The Linux kernel will definitively never be rewritten in affine C.

[0] https://en.wikipedia.org/wiki/Substructural_type_system


"strict mode" is not a subset - you need stronger type information than what C provides to get Rust level checking - if you wanted to add that to C and also make it expressive enough to be usable you'd eventually end up with something that's closer to Rust than C.


This exists, and is called static analysis. There are tools that can analyze code paths and build a theoretical model of lifetimes throughout a problem.

Mind you, I don't know the formal theory enough to say whether you can build a static analysis tool that is actually capable of doing what Rust's lifetimes accomplish in a provably complete manner. Maybe someone else can chime in here.

However, there are other disadvantages to this approach. Not having it in the language means everyone working on the program has to be using the exact same static analysis tools, and they have to be using it on the entire program, including libraries. Rust ensures that the lifetimes are enforced consistently, since there's only one possible engine enforcing them.

There are some very impressive code analysis tools used in the industry (with C and C++, in particular), but far as I know, they are all commercial and fairly expensive. As far as I know, open source tools like Valgrind are not powerful enough.


Even if such a tool exists it would be along the lines of a tool that prevents your C code from containing turing complete constructs so that it can be formally proven. It will complain about perfectly valid code. Are you willing to use a tool with false positives? I've seen many C programmers that are highly confident in their abilities. If someone told them to use a tool that marks their obviously correct code as "potentially wrong" they will just stop using that tool the same way they refused to use Rust.


That is exactly what is expected from developers that need to certify their code via MISRA, AUTOSAR and other security critical certification processes.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: