Hacker News new | ask | show | jobs
by shuger 1456 days ago
I work in probably what is considered one of the least "safe" languages: C++

The issues that Rust is supposed to help with are simply not what we spent time on. All the bugs reported are pretty much exclusively root caused to "business logic".

From recent time I can recall only one that was a programming mistake and not architecture/business logic related. It was a missing break in a switch that already had some fallthroughs so it didn't look incorrect at a glance.

I do understand what Rust is supposed to provide but in practice it's simply an extremely minor source of bugs.

10 comments

I see many C++ programmers say stuff like this, and maybe it is the case that studios exist which can do C++ without mistakes related to memory management or sigils and so on, but I also observe that:

1. High quality teams like the Linux kernel team and PostgreSQL, do periodically have serious security bugs that are things Rust would have caught.

2. I see sometimes C++ instructors making the same claims you do and then spending half of a lesson tracking down a memory mistake, (Casey Muratori) or in the same month a tweet from a game engine developer saying they don't really see the value of garbage collection and then tweeting about how they spent 48 hours tracking down a memory mistake. (of course, for a game engine that is what you have to do sometimes!)

There are however valid questions, like if Rust slows down your development say, 5%, would you get more net safety from spending 5% more time testing/fuzzing c++ code instead? etc.

> High quality teams like the Linux kernel team and PostgreSQL

Neither of those code bases are C++, which significantly dilutes your point. A major benefit of modern C++ is that it is much safer than the language Linux and PostgreSQL are written in.

Chromium _is_ written in C++, has some of the best coders and one of the best-tested codebases in the world. They're still finding zero-days based on exploits due to the language's failings.

There's a reason Mozilla has decided that all parser code should be moved to Rust ASAP.

> High quality teams like the Linux kernel team

Which very notably does NOT use C++.

Nor does Postgres.
I'd venture that the development slowdown from using rust is closer to 50% than 5%. Even just compile times probably slow down that much, not to mention you have to write (sometimes) as much as 10x code to express yourself.

The specific exercise I have in mind is a lockless thread queue. < 20 lines in C .. ~200 lines in Rust.

My anecdotal experience is the opposite.

I write code much faster in Rust than in C++. Part of it is thanks to the type system – fewer opportunities for dev errors means that I can produce code that is more concise, spends less time handling runtime errors because I can have static guarantees that they have already been handled somewhere, etc. Part of it is #[derive(...)], great documentation, Cargo and other QoL components of Rust.

Fair enough. I can imagine if you largely write code that plays nice with the compiler it could be faster. That just hasn't been my experience.
> The specific exercise I have in mind is a lockless thread queue. < 20 lines in C .. ~200 lines in Rust.

Why is this your exercise you have in mind though? This is such a bad argument. Like, yes, if you work at doublylinkedlist.com where your entire job is writing a new linked list implementation every day of the week, Rust might be a bad choice. But that's not what any kind of commercial enterprise actually looks like. If I saw you writing a lockless thread queue at work, I'd tell you to stop wasting time.

>> Why is this your exercise you have in mind though?

That was an example I could point to where the code size difference between Rust and C or C++ is roughly 10x. I mentioned it to corroborate the claim that sometimes writing Rust is very verbose, on the order of ten times more.

My point was not that average commercial codebases are largely dominated my these types of structures. My point was that my experience with Rust has been that the development slowdown is much more than 5%, as the OP suggested.

>> If I saw you writing a lockless thread queue at work, I'd tell you to stop wasting time.

And that's one reason we don't work together ;)

A lockless thread queue (not entirely sure what that would be, sorry; a queue in front of a pool?) seems like the kind of low-level library that could be written with liberal use of unsafe. No need to flagellate yourself on the altar of compile-time safety.
Then why would I use rust in the first place?
So that you can write "primitives" such as a queue in unsafe, and assuming it is correct, it is not possible to introduce bugs from other code.

Also, things like queues tend to be implemented in either the stdlib or a very popular library. So they are very well tested and likely to be widely reviewed.

I'm sure there is a ton of variance. I have done some Rust projects where I run into absolutely no safety complaints from Rust because it just isn't the kind of code that does anything the borrow checker cares about. For those development time ends up typically faster than C/C++ due to various syntax and tooling niceties.

Other projects will really get into domains where you have to work hard to satisfy the borrow checker and it can slow you down a lot. In a real application you won't be writing lockless thread queues for a big % of the time. But then for a real application the compile times will start to weigh on you more. (Though, C++ does not always compile fast either unless some care is taken to be sure it does)

And you're running at least one static analysis tool and a linter on your C++ code too, right? You're not just deploying the C++ compiler output as-is, right?

Those take time to run too and should absolutely be added to the compile time metrics when comparing against "cargo build".

On a personal project I run 2 metaprogramming passes, compile the entire project (~2m LoC) _twice_ and run the entire test suite in < 30s on a laptop from 2010.

I don't run a linter because I hate them, and my metaprogramming passes do a bit of extra static checking clang doesn't. I occationally run extra static tools (valgrind et. al) but they're painfully slow and very rarely catch anything.

Rust in incremental build mode is not slow at all, I really don’t see why people say so.
>The specific exercise I have in mind is a lockless thread queue. < 20 lines in C .. ~200 lines in Rust.

Do they have an equivalent API? Rust does have a hard time explaining very, very low level stuff that you'd have to do unsafe and various magicks. But once you get the unsafe details right, the consumer API for them tends to be extremely rigid and fool proof.

> Rust does have a hard time explaining very, very low level stuff

A little odd for a supposed system langauge

But it is just not true. Rust has a hard time writing algorithms where objects don’t have a singular owner. That’s all, it is not low level stuff. It for example has proper SIMD support, while the supposedly low level c doesn’t.
This is an ... interesting argument. "I can't write this 200LOC component of a large codebase in safe Rust, so what's the point in Rust?"
> High quality teams like the Linux kernel team and PostgreSQL, do periodically have serious security bugs that are things Rust would have caught.

Safe rust would have caught. If you had to drop into unsafe to do what they did, the serious security bugs would still have happened.

A fair point, though the few security issues in recent years I've looked at were not the kind of thing you would turn to unsafe rust for. But I've certainly not done a broad enough sampling to say what % of cases are like that.
To be fair, I am sure Rust catches a fair amount (compared to C++ which would catch 0). I just think that phrasing rust has having 0 memory errors can be a tad inaccurate.
Except when something goes wrong, you can typically focus all your attention on the unsafe blocks, which should be a very small portion of your codebase, if it exists at all. (Contrary to popular belief, unsafe Rust code is neither mandatory nor widespread.)

By contrast and comparing apples to apples, your entire C or C++ codebase is the equivalent of one giant unsafe block. You can bring in 3rd party tools to perform some of the static analysis the Rust compiler does for you, but call it for what it is.

By default, Rust is safe while C & C++ are not.

> There are however valid questions, like if Rust slows down your development say, 5%, would you get more net safety from spending 5% more time testing/fuzzing c++ code instead? etc.

I think a similarly valid question is how often you resorted to dynamic allocations just to get the borrow checker off your back. If your Rust version uses 5% more dynamic memory (with the corresponding performance and memory footprint penalty), is it perhaps worth staying with C/C++ and spending more development time on testing/fuzzing?

Some people make the opposite claim: Rust lets you get away with less dynamic allocation (and more data shared between threads) because you can rely on the compiler checking your work.

The effect might be positive or negative depending on the circumstances.

If you're willing to jump back to C++ to get around the borrow checker strictness, couldn't you just use Rust's unsafe block where you're sure it won't result in a bug?

Is it harder to fuzz Rust? Honest question, because fuzzing is something I occasionally read about but am not practiced in.

I've found it very easy to get started with fuzzing in Rust using cargo-fuzz. I didn't do anything very advanced, and my closest point of reference is testing Python with Hypothesis, but it did turn up bugs.

Here's a Rust fuzzing story from yesterday: https://hacks.mozilla.org/2022/06/fuzzing-rust-minidump-for-...

It claims that Rust is particularly suitable for it because integer overflow panics in debug builds (and out of bounds indexing always panics), which sounds reasonable.

> And what did we screw up? Some legit stuff! It’s Rust code, so I am fairly confident none of the issues were security concerns, but they were definitely quality of implementation issues, and could have been used to at very least denial-of-service the minidump processor.

This is so much better than the outcome would have been in any C or C++ project despite the many protestations of "just follow modern best practices" adherents. The author of minidump is no novice, is well versed in best practices in multiple languages including C++, was sure the code was solid, and still got spanked hard by the fuzzer. Denial of service outcomes aren't ideal, but they were likely fewer in number and are unambiguously better than security vulnerabilities.

That is a fair thing to wonder about, but at the moment most of the design decisions Rust pushes you toward tend to be better for performance on modern hardware. For instance using an array based arena to store a graph, instead of the traditional allocation of a chunk of memory per node on the heap. Or just keeping more stuff on the stack.
But it takes a lookup hit and you need an arena per object type with its memory overheard. You'll get more cache misses.

A lot of the rust performance talking points just aren't true. Rust is slower than C and C++, not by much but you can't get a true believe to even recognized this. Rust has turned into a religion.

Rust also disallows some things that you can do fine on C++ if you know our architecture because thing won't work on some machine 20 years ago (eg, it has more strict alignment requirements than any machine a consumer can see).

Throughput may only be 5% or more slower, but latency issues for rust is a much bigger issue. The devs I've talked to don't even try to pretend they have a good latency profile.

The linux kernel is absolutely anything other than high quality lol
I've worked in a variety of languages, and returning to a c++ project recently I do see that we spend a lot more time thinking about how to write the code in a way that avoids problems. Meaning that there's a lot more architecturing required to reach a sane state.

We have a sister product written in a dynamic language, and sometimes we have identical functionality.

I've noticed that when a change is discussed, the c++ gang has architectured themselves into the current solution and therefore have a much harder time making changes.

So for that reason I think it's easy to overlook these complexities when you're working in c++ alone; they feel natural and are just part of how you work. You forget that a lot of this architecturing just isn't necessary in a lot of other languages.

Maybe I'm wrong, but I believe that most of the architecting that you describe would be effectively what you do with regards to performance as well: Minimizing change of ownership, moving to a system with more static allocations with fewer "objects" that are linked into a variety of subsystems.
Yes, that's true. In a sense, c++ requires good code structuring.

That's also part of why I enjoy returning to c++, the people involved know how to structure code and create clean architecture.

That said, sometimes c++ does get in the way. Creating trees or graphs can be cumbersome, and IMO it's very biased towards virtual methods to solve polymorphism.

Extending lifetimes by pooling or similar is also quite common, and is in my eyes sometimes overdoing it. If you for instance use Rust, you can be a lot more confident that the compiler catches these issues, and be more conservative and efficient in the solution.

> very biased towards virtual methods

This is a function of the type of software you write. There are many large C++ code bases that rely on various types of static polymorphism almost exclusively, rarely having a use case for virtual methods or dynamic polymorphism. There is a similar story with inheritance versus composition; some types of code bases naturally gravitate toward one or the other.

The nice thing about C++ is that it as amenable to any of these models should it benefit the application.

I don't agree. The support for static polymorphism is in it's infancy at best.
Can you give some examples of what you mean? I use static polymorphism in C++ routinely and haven't felt particularly limited by it.
> and IMO it's very biased towards virtual methods to solve polymorphism.

Is it? These days I'd expect C++ to be very biased towards using templates for polymorphism. After all, templates are a thing that C++ provides with functionality that other languages often lack, whereas in the field of virtual methods/"dynamic dispatch OOP" C++ severely lags behind other languages. Choosing between two features of a language and using exactly the one that is worse in comparison to your competition feels wrong to me.

I do not think C++ "lags severely in the field of virtual methods". I use both OOP and template based polymorphism depending on particular needs and see no significant problems in either.
C++ can't do things today that Smalltalk and CLOS were able to do in the 1980s already; how is it not lagging behind in OOP? Hell, companies like Trolltech had to extend C++ for their own purposes to provide just a subset of the extra features (relative to standard C++) that had already been available in environments like Smalltalk or CLOS.
> Creating trees or graphs can be cumbersome

Wouldn't Rust have the same problem with this (if not worse)?

I was told that deferred_ptr/deferred_heap was supposed to solve these things for C++, so perhaps that would make Rust the option with worse problems in this department. Not sure where it got by now, though.
Sometimes (definitely not always) you can use std::function instead for dynamic polymorphism.
…and when you do need a large number of dynamically allocced/deallocced objects, then using indices to arrays instead of pointers. Which kinda defeats the purpose of using the Rust borrow checker…
I think this mirrors my day to day experience with C++.

On the other hand, fuzzing large c++ programs will routinely uncover memory safety issues in practically any large codebase that hasn't been absolutely beaten to death by fuzzers already.

The issues are not usually so much "I returned this thing on the stack" they tend to be things like "this (very unexpected) sequence of api calls will result in a UAF in this deeply nested data structure over here on the heap".

> The issues that Rust is supposed to help with are simply not what we spent time on. All the bugs reported are pretty much exclusively root caused to "business logic".

Everyone claims this, probably because business logic bugs are more memorable. But I've never seen it match the real statistics. According to the best published data, null alone is something like 30-70% of bugs, you just don't remember them because they're uninteresting.

It depends on the person's mental categorisation of bugs too. For example I could see someone classifying null bugs as business logic bugs because "the business logic didn't account for that information not always being there"
It's a language bug, really. If you're expected to say what the individual things in your program are allowed to be (like you have to in C++, Java, C# etc.), and you say "foo is a BarBaz object", and the language and compiler allow you to set foo to something that isn't actually a BarBaz object and this is considered OK, then the language is botched.
There are and never will be (meaningful) statistics for the "N percent of bugs are caused by X" question.

Every org's use cases are different, how do you get (let alone compare) data frm different orgs, who really counts their bugs anyway (and those that do at scale and in detail are probably doing suffering from some form of myopic management disorder or another), etc.

All you can do is ask people their gut take based on their particular experience. For systems engineers, a lot of bugs are due to memory safety. For more consumer-oriented startups (or in most any bigcorp), yeah, it's "business logic" (or people's inability / unwillingness to communicate), etc.

"We found that 70 percent of our bugs could have been prevented by moving to TypeScript", yeah sure.

I wonder if passing NaN is considered "business logic" Thinking of this interesting exchange long ago by an experienced c++ dev

https://old.reddit.com/r/rust/comments/78bowa/hey_this_is_ky...

Counterpoint: When I was on the Windows accessibility team at Microsoft, one of my most brilliant colleagues gave a presentation about what to look for when doing code reviews, and he emphasized three main categories: C++, COM (Microsoft's Component Object Model), and concurrency. Rust eliminates many of the issues in the first and third categories. And yes, we were writing modern C++ as much as we could in that legacy codebase; by 2019 we were using several C++17 features, as well as the latest C++ utility libraries for working with COM and WinRT. Given the state of the Rust windows crate, that team (which both I and that colleague left in late 2020) might even be able to use Rust in new code. I'm sure that would make him happy.
My manager told me exactly the same thing as you do; after I found a few UB in its code in the first 2 weeks he changed its stance.
I had to deal with a senior dev. that gave me a talk about seniority after I ran valgrind over our software. Guy was so deep into the whole senior dev. power trip that he blamed third party libraries for his bugs, dev. tools for "incorrectly" identifying his bugs and wrote more bugs to work around his other bugs.

Finding and fixing issues in C++ code can be easy with the available tools, getting people to use them on the other hand can can be like talking to a wall.

That sounds more like an expert beginner than senior.
Sounds like someone who senses his place in the hierarchy is threatened.
Unsurprisingly the types of bugs reported are going to be around business logic errors and not obscure edge case that users won't run into naturally. The bugs are still there though.
Right, and if you come across it you can't always reproduce it. "Oh it crashed, yeah it does that sometimes".
>The issues that Rust is supposed to help with are simply not what we spent time on.

There could be plenty of bugs in your codebase but just because you don't spend time on them doesn't mean they don't exist. Hundreds of millions of people used OpenSSL everyday for 2 years after the Heartbleed bug was introduced. It didn't cause any obviously broken code until someone exploited it to read credit card numbers off of a remote server.

Can you elaborate on the proficiency of your dev team, is this with juniors etc? Is it a large team? And what is the complexity of the project? I think this is important information
GPU driver, most devs are senior. Hundreds of thousands of lines of code in the "slice" my team is interested in. Team for our component has on it's own has probably over 40 people.

Driver should be even more prone to programming bugs because most of it is about manipulating data in raw "untyped" memory.

GPU drivers are also some of the most buggiest stuff out there I used. When I worked in Games we routinely managed to make the GPU drivers crash which thankfully at the time was already no longer taking down your engine machine.
My experience with GPU drivers then is that your code is likely buggy AF and your team is in denial.
In practice for you. Where I am, things are very different! Buffer overflows and memory corruptions, threading issues, uninitialized variables, etc appear on a very regular basis, and end up being very difficult to debug, mostly because the moment where you corrupt memory and the moment it triggers a bug can be very far apart.