Live data from Hacker News

Safety: A comparaison between Rust, C++ and Go

nested.substack.com

171–180 of 188 posts

Re: Safety: A comparaison between Rust, C++ and Go

#171

Earlier quoted context omitted.

Yep, my favorite part of heap/stack corruption is not when it crashes immediately but rather when it rears its head 2-3 weeks/months later when some upstream call pattern or timing has changed. I've spend weeks chasing down single instances of this on multiple projects. The nasty part is you have no predictability in if it's going to be one that crashes immediately, silently writes garbage(hopefully not to disk!), is…

Yeah, this just isn't how it is anymore. The last time I was up shit creek because 50k boxes were crash looping and GDB couldn't get me a stack trace was in 2014. The last time I spent more than 30 minutes chasing a memory corruption issue was in like, 2018. And it was because some wise ass had decided to roll his own fibers by stomping on `rip`, `rbp`, and `rsp`. These days you use `std::unique_ptr`, build with clan…

A sibling comment suggests this has more to do with where you work than how modern your C++ is, which rings true to me. Different kinds of programs need different kinds of memory management patterns, and some are more error-prone than others.

In my experience there also tends to be a long tail of memory corruption bugs. After flushing out those that are easy to run into or that have a major impact, everything seems fine and you can go years without really spending time on them, but they're still lurking at the edges of automated crash reports and mysterious bug reports you never quite manage to reproduce yourself. And when I do manage to track one down, it's as likely as not to be in, around, or even caused by modern C++ features.

Tetris puzzle or not, it's really quite nice to systematically rule out those kinds of issues. In some domains it may not be worth it, but in others they can hide major security issues or similar. And either way it sure beats periodically digging through crash dumps trying to piece together something that looks impossible from the surrounding source code.

Re: Safety: A comparaison between Rust, C++ and Go

#172
post #155

Earlier quoted context omitted.

can you elaborate on those key missing language features ? You have commented multiple times about that, but haven't seen you giving any concrete example. I'm Genuinely curious.

Where to begin? Operator overloading. Programmed move semantics. Generics argument concepts. Look at the whole list of features C++ got since C++11, and subtract out the few of those Rust had or got.

I've done a toy matrix library with operator overloading in Rust a while ago. Could you be confusing it with Java?

  #[derive(Debug, PartialEq, Eq)]
  pub struct ColumnVector([F; DIM]);

  impl Add for ColumnVector {
      type Output = ColumnVector;
      fn add(self, rhs: Self) -> Self::Output {
          Self::new(self.0.zip(rhs.0).map(|(a,b)| a + b))
      }
  }
Usage:

  let v = ColumnVector::new([1.0,
                             3.0,
                             4.0]);
  let w = ColumnVector::new([1.0,
                             0.0,
                             4.0]);
  assert_eq!(v + w, ColumnVector::new([2.0,
                                       3.0,
                                       8.0]));

Re: Safety: A comparaison between Rust, C++ and Go

#173
post #155

Earlier quoted context omitted.

can you elaborate on those key missing language features ? You have commented multiple times about that, but haven't seen you giving any concrete example. I'm Genuinely curious.

Where to begin? Operator overloading. Programmed move semantics. Generics argument concepts. Look at the whole list of features C++ got since C++11, and subtract out the few of those Rust had or got.

> Where to begin?

I propose you start with genuinely concerning problems. Operator overloading is a first-world problem and I've made a good career never depending on it (outside of my active C/C++ years at the start of it).

"Generics argument concepts" says exactly nothing, to me at least. Elaborate?

"Programmed move semantics" is, if I understand you correctly, a flavor / taste thing but I can agree it can be made better and more explicit -- say by not using the `=` operator for it. That I could stand behind. Still, it's only catching you off-guard while you're learning. 50/50 though. It's a concern but IMO not a major one.

And your final sentence betrays bias to C++. Well fine, use that, nobody is forcing you to work with Rust, right?

But if you're willing to bash Rust, please do so with concrete arguments. If you know something negative about it that I don't, I believe I and many others will benefit from informed objective criticism.

Do you have that? Already asked you in another comment and I'm still willing to listen to it.

Re: Safety: A comparaison between Rust, C++ and Go

#174
post #4

Earlier quoted context omitted.

Your point is a fair one, but defaults matter. There's little reason for clang-tidy not to be part of the default clang invocation by now, other than an aversion to producing new output for existing projects (that you could argue are already "broken"). Unless clang-tidy has false positives, in which case the comparison isn't apples to apples then.

> Your point is a fair one, but defaults matter. This sort of comment is far from fair and misses the whole point. The thesis of this article is how programming languages compare with regards to safety. It makes no sense at all to compare particular implementations and try to pass personal assertions on particular features of said implementations as broad assertions about the programming languages they support. So a…

As is clear from my other comments I also find this stuff unfair.

I try to keep in mind that on balance it’s a good thing that performant programs have become dramatically more accessible recently, and that most of the C/C++/Fortran/Haskell antagonism is a result of enthusiasm around that. For me it was BASIC -> C, but I imagine JS -> Rust is every bit as exhilarating.

But I do hope at least a few people read your comment and are inspired to learn a little background. Rust is cool because it remixed the broadly-accessible FP algebra, a kickass C++ toolchain decades in the making, and a big bet on linear typing.

I didn’t set out to be the jaded, un-hip old guy but here we are :) It’s a nifty new LLVM front end with type classes and the Either monad. Neat. Get off my lawn ;)

Re: Safety: A comparaison between Rust, C++ and Go

#175
post #107

Earlier quoted context omitted.

Yes, when they care about data consistency in distributed systems. Maybe many Rust devs don't care.

So what is your point? You’re changing the goal posts on safety and it’s a pointless reductive argument. Even if we account for everything, solar storms will eventually flip bits unexpectedly. Does that make Rust’s guarantees worthless?

The goal posts stay on the same place. There are ways towards data corruption where Rust's fearsome concurrency is of no help.

Re: Safety: A comparaison between Rust, C++ and Go

#176
post #171

Earlier quoted context omitted.

Yeah, this just isn't how it is anymore. The last time I was up shit creek because 50k boxes were crash looping and GDB couldn't get me a stack trace was in 2014. The last time I spent more than 30 minutes chasing a memory corruption issue was in like, 2018. And it was because some wise ass had decided to roll his own fibers by stomping on `rip`, `rbp`, and `rsp`. These days you use `std::unique_ptr`, build with clan…

A sibling comment suggests this has more to do with where you work than how modern your C++ is, which rings true to me. Different kinds of programs need different kinds of memory management patterns, and some are more error-prone than others. In my experience there also tends to be a long tail of memory corruption bugs. After flushing out those that are easy to run into or that have a major impact, everything seems f…

If you need extreme robustness you have to have coverage and fuzzing and canaries and stuff for logic bugs as well as memory bugs. If you’ve got a long tail of non-exercised code paths, a “” will fuck up your day just as bad as a use-after-free.

If your code is covered, ASAN will red-zone the memory bug. It checks every address.

People are welcome to their subjective opinions about the “easiest” way to get truly correct software (which almost no one needs), but the oft-repeated assertion/implication that the tools don’t exist to do it outside of Rust/Go is wrong. Not a subjective opinion, demonstrably incorrect.

And when enough truly important shit is written in Rust, which will be soon, there will be CVEs. Many of them.

Re: Safety: A comparaison between Rust, C++ and Go

#177

Rust has a lot of great qualities that C++ lacks, but comparing `rustc` to `gcc` or `clang` on move-semantics checking is just kind of silly these days. `rustc` has `clang-tidy` built in. `clang-tidy` is not letting you mutate or even access that moved-from "suffix" object without throwing an error. It's annoying that you need `clang-tidy` and ASAN and shit to get comparable runtime safety even in greenfield C++, but…

To say the rust has a built in linter is wrong. A rust program that does not build because of a memory ownership error on the part of the programmer isn't rejected by the compiler due to a detected pattern, it is rejected because the program is "unsolvable" and cannot be built. I think a lot of people miss how integral the memory semantics Ruct enforces are to how it parses and compiles programs. If these things were simply lints it could be reasoned that a program could be built without following these semantics, that If you could simply go into the compilers source you could just turn them off/remove them. You can't. The way rust's compiler tracks memory is fundamental to how it compiles the binary. It is not simply pattern matching code or ast. Rust's compiler is actually tracking the lifecycle of every bit of memory allocated so it knows when to free it, and it does this at compile time without running the program. These memory semantics errors exist because they are integral. Turning them off would simply result in a broken compiler, or a program with no freeing of memory because the compile time reference counting rust implements becomes impossible.

Re: Safety: A comparaison between Rust, C++ and Go

#178
post #171

Earlier quoted context omitted.

A sibling comment suggests this has more to do with where you work than how modern your C++ is, which rings true to me. Different kinds of programs need different kinds of memory management patterns, and some are more error-prone than others. In my experience there also tends to be a long tail of memory corruption bugs. After flushing out those that are easy to run into or that have a major impact, everything seems f…

If you need extreme robustness you have to have coverage and fuzzing and canaries and stuff for logic bugs as well as memory bugs. If you’ve got a long tail of non-exercised code paths, a “ ” will fuck up your day just as bad as a use-after-free. If your code is covered, ASAN will red-zone the memory bug. It checks every address. People are welcome to their subjective opinions about the “easiest” way to get truly cor…

Well, yeah, if you're reaching for that level of robustness you want every tool you can get. If you can get rid of a whole category of bugs with one tool, that only makes the other tools more effective for the rest!

(There are also cases where that extra robustness is more of a "nice to have," so if you can get a side effect of your approach to something more important, that changes the calculus too.)

Re: Safety: A comparaison between Rust, C++ and Go

#179

Earlier quoted context omitted.

The (default) hash for Rust's HashMap and HashSet is a SipHash. People shouldn't call this a "cryptographic hash" or a "crypto hash" - that's misleading as it would lead you to think of algorithms like the SHA-2 family - but this is literally a cryptographic algorithm just one with very specific properties suitable for this task. Such algorithms are crucial to avoid being subject to a Denial of Service attack which i…

I clearly spoke out of turn when I mouthed off about Rust's table not defaulting to a Swiss design, and I thank you for straightening me out. But to the degree that C++ has an RTFM vibe, and I really don't think you'll hear Andrei or Meyers or Sutter talking that way much, it's uniformly applied and not particularly partisan. In my experience C++ pros would rather be writing Haskell and that's where you get all these…

> I'm calling bullshit on the every damned thing needs to be DoS or timing-attack hardened

But that isn't the claim. Rust's defaults are safe. Remember Rust's one line description "A language empowering everyone to build reliable and efficient software".

This is like with the decision that Rust's sort() is a stable sort. I know what a stable sort is, and so do you, so if we care we may decide it's appropriate to use the unstable sort which could be faster. But programmers who don't know what a stable sort is aren't expected to learn about it before their sort does what they expected.

Same here, I know that SipHash is slower than Fowler–Noll–Vo, which in turn is slower than the identity function, and I know why it would or would not be OK to choose them, and presumably you do too. So if we care we may choose a different hasher for our HashMap. But programmers who don't know about hash algorithms aren't expected to go learn all this stuff before using HashMap.

I think maybe C++ isn't programming it's actually a live action "Um, actually" game where the stakes are your program arbitrarily misbehaves unless you correctly guessed all the things wrong with whatever code you just wrote despite the compiler insisting there's nothing wrong with it as written.

Could I do OK at that game? I'd like to think so. Do I want to play? No thanks.

Re: Safety: A comparaison between Rust, C++ and Go

#180

Earlier quoted context omitted.

Yeah, this just isn't how it is anymore. The last time I was up shit creek because 50k boxes were crash looping and GDB couldn't get me a stack trace was in 2014. The last time I spent more than 30 minutes chasing a memory corruption issue was in like, 2018. And it was because some wise ass had decided to roll his own fibers by stomping on `rip`, `rbp`, and `rsp`. These days you use `std::unique_ptr`, build with clan…

I deal with issues like this about once a month. It may not happen where you work, but it definitely still happens. If it really never happens where you work, consider yourself lucky.

Is every diff thoroughly reviewed? Is everything built with `-Wall -Wpedantic -Werror`, `clang-tidy` with most checks on, ASAN/TSAN/MSAN/UBSAN on every commit in the CI, and aggressively canaried against replay data (or whatever is appropriate to the domain to exercise all the paths)? Is all the code run through `clang-format` in a pre-commit hook to lower the cognitive overhead of spotting bugs?

I completely understand that when you turn all the checks up to maximum (which, in fairness, `rustc` does by default) you start with as many errors as you have files if you're lucky, and probably 10x that. I've had to take codebases from working by accident on every 10th line to passing all the static analysis cheaper than PVS-Studio, and it's a bear no doubt. But codebases that are `clang -Werror` clean, `clang-tidy` and `cppcheck` clean, ASAN/MSAN/UBSAN clean, and have all this enforced by CI?

I haven't seen those codebases thrash the core dump where GDB prints out a bunch of "????????????" instead of addresses with any frequency.

Someone should do a 2022-edition "Joel Test" (https://www.joelonsoftware.com/2000/08/09/the-joel-test-12-s...) because I think we're all using revision control now, times change, but until someone does, I'm happy to trade war stories about getting messy code bases / development workflows into fighting form.

Post reply on HN