Rust is much more robust about catching those cases and verifying that you don't have objects outliving their references.
That's something that the programmer needs to specifically ask for, and I'm not aware of any C++ compiler which doesn't throw a bunch of warnings regarding an object's scope and lifetime.
That means your point boils down to arguing that if a programmer really wants to, he can still shoot himself in the proverbial foot by writing C++ code, but in order for that to happen he needs to have a death wish and intentionally avoid all warnings and safe practices to begin with.
Requires manual annotation and isn't nearly as systematic and comprehensive as the Rust compiler, but I've found threading bugs with this in existing code that I was about to modify.
#include <vector>
int main()
{
struct Foo {
int bar;
};
Foo* bar;
{
std::vector<Foo> foos;
foos.emplace_back(Foo{1});
foos.emplace_back(Foo{2});
bar = &foos[1];
}
bar->bar = 2;
}However, I never spent more than 10 minutes fixing that.
Debug version of MS C runtime fills freed heap memory with a magic number 0xFEEEFEEE. You’ll get exception in runtime, and you’ll immediately have the idea what’s wrong with the code.
I've seen 5 minute fixes and things that took weeks to track down(yay for non deterministic repros).
Technical support guys were not that happy with the customer regularly calling them to know when the fix would be delivered.
Have other war stories about tracking down leaks as well.
Most commercial libraries are written in C or C++. The reasons include performance, smaller runtime, and ease of use (all OS kernels are written in C, so the majority of languages already feature decent C interop). Even if you code Rust, you won’t have that compile-time checks inside proprietary third-party libraries.
FWIW it's getting pretty common in the Rust ecosystem to wrap 3rd party libs with proper lifetime annotations with opaque types when possible. Things like OpenGL, JNI bindings and whatnot can still benefit from lifetime annotations the compiler provides.
Rust proponents often overestimate practical impart of those C++ safely problems fixed by Rust.
At the same time, they often underestimate or ignore Rust’s problems caused by the same safety thing. The main of which is, it’s hard to implement some widely used data structures in Rust. Here’s my old thread where I discuss the topic in details:
Are there categories of UAF that would not be caught with valgrind / asan? Or is it the case that the test suites do not have sufficient coverage to exhibit the issues?
We were testing our application the night before a big demo for our client. Ours was a C++ OpenGL application with several "phases" the user could enter from a "main menu" type screen. Our demo explored these phases in a pre-planned order. All went well, until somebody decided to try doing the demo in a slightly different order. Once the tester visited a certain phase of the application, the thing crashed and burned. The stack trace was littered with some incomprehensible container-implementation-detail junk. Debuggers weren't pointing at an obvious sign, other than that something was dereferencing a garbage pointer.
We noticed that if we went back to the original canned order of operations (this was a demo, after all), the application didn't crash. But this still made us nervous. We found that we could sometimes swap a few phases early in the application's run with no ill effect.
Here's part of the class responsible for some of the higher layers of the stack trace, from one of the lower level libraries we build all of our applications on (this is important because, for hysterical raisins, we can't source-step debug those libraries in production). Can you spot the bug?
#include <map>
#include <string>
#include <vector>
struct Message;
class MessageCentral {
public:
void HandleMessages() {
// messages were collected somewhere and organized by their queue.
const std::vector<std::string> queues_with_new_messages;
for (const std::string &queue : queues_with_new_messages) {
const std::vector<Message> msgs; // = messages_for_queue(queue);
const auto &handlers = handlers_[queue];
for (auto &h : handlers) {
for (const auto &msg : msgs) {
h(msg);
}
}
}
}
void RegisterMessageHandler(const std::string &queue_name, std::function<void(Message)> handler) {
handlers_[queue_name].push_back(handler);
}
private:
std::map<std::string, std::vector<std::function<void(Message)>>> handlers_;
};
(Note: this is probably way easier to figure out with all of the other code needed to implement MessageCentral taken out! It took us three engineers working for about 2.5-3 hours each, with lots and lots of gdb and printf debugging, to nail this one down. This particular crash would have been impossible in Rust, because the compiler would have rejected the Rust equivalent of the above C++ code.)(oh, by the way, we couldn't use runtime memory sanitizer tools like ASan or valgrind because the application linked to CEF. In retrospect, we could have dropped in a fake CEF that didn't interfere with ASan or valgrind, but that would have required build resources we didn't have available in the frenzied night before a big demo.)
I assume one of your handlers invokes RegisterMessageHandler invalidates the iterators to handlers_[some_queue_name]. As such, the following line exhibits UB:
for (auto &h : handlers) {
In practice, the exact behavior is likely to depend on if the vector resized it's underlying storage or not.EDIT: I'll also note that using a debug build of the SC++L with iterator debugging enabled should give you a useful diagnostic here - given a modern SC++L implementation. Whether or not your application runs well enough, to actually get to the point of being able to reproduce the bug with iterator debugging enabled, is another matter entirely of course.
EDIT x2: I'll further note I'm cheating here by having encountered and fixed this same bug on a different codebase.
No, it's not.
Use-after-free is very easy. Just push onto a vector while you're iterating over it, to name one obvious example that comes up a lot.
What I have learned since learning it in 1993, using it including teaching the language to first year students, is that any safety feature that isn't imposed by the language instead outsourced to best practices or external tools tends to be ignored by the majority.
Also, this is just me, given experience writing code in C++ and Rust, I find Rust to be a more ergonomic language (YMMV).
This is what I mean in another thread about outsourcing safety.
Then you cannot use Rust and must settle for lack of safety. (A profoundly silly question -- if modern C++ is not an option for whatever reason, then Rust is doubly so.)
Just because the only existing Rust compiler uses LLVM, it doesn't mean another implementations will not surface.
So hypothetical the OS not targeted by clang, can still have a Rust compiler on the OS SDK, offering all safety guarantees from Rust.
For example, there are OSes and hardware architectures not supported by clang that have Ada and even Java AOT compilers.
If you want an option that works today without nonsense as you put it, then I would pick Ada over C++ for secure critical projects in such scenarios.
Companies like LDRA do exist, because just using clang isn't enough.
http://www.ldra.com/en/software-quality-test-tools/group/by-...
Also a reason why HIC exists http://www.codingstandard.com/
more info: https://blogs.msdn.microsoft.com/vcblog/2016/10/12/cppcorech...
As a long-time user of C++, I find Rust to be more appealing in many ways. I don't think "C++ is dangerous" is a good approach to advertising Rust to the C++ community.
A good way to see what C++ community cares about, in order to make Rust appealing to them, is to see the work from SG14 (games and HPC) on ANSI C++.
That is the type of crowd that Rust should target, and they care about performance without belts above all.
Working with teams on typical enterprise environments is another total different story.
Just last week I had to do an acceptance review of a C# application for a customer, I surely wouldn't want those devs touching C++ as well.
If you're writing Firefox or SSL, a memory mistake may be a vulnerability and a serious issue. In games, hpc, hft, etc, a memory mistake is almost always just another bug.
Would be great instead to see more examples of how e.g. destructive move in That allows generating better assembly.
One example isn't about the destructive aspect: a move in Rust is always a memcpy. Since there's no move constructors, there's no way to run arbitrary code, so it's always a memcpy. The optimizer can also then elide this copy if it isn't actually needed.
In HPC a memory mistake is yet another way of producing corrupt results for writing that paper, that after all is wrong.
In HFT a memory mistake is yet another way of producing corrupt values, leading to the automated trading bot selling items way lower than they should have been, loosing millions in the process.
I'm not a game developer so I can't judge your estimation of bug severity there but it seems highly speculative to say the least.
On the other hand your comments vis-a-vis HPC and HFT show that you have very little domain knowledge. Memory corruption is far more likely to crash a program, or to cause results that are wildly wrong. Wildly wrong is not so bad; any scientist worth his salt will question the results and see there are issues.
Your comments on HFT are even worse. Exchanges will not accept "selling items way lower", placing orders too high or too low produces rejects. And even if the order is accepted, it simply crosses the book at the best prices on the book. If you want to write about how you really lose millions, read about Knight Capital. Nothing to do with memory.
In both HPC and HFT, subtly wrong is a much bigger problem than wildly wrong. And memory is much less likely to produce subtly wrong unless someone is deliberately exploiting it. Again, it's not a matter of this never happening. It's just that it's rare in comparison to all of the other ways in which you can make mistakes, and particularly subtle mistakes.
This really seems to be one of the problems with Rust. Most people involved seem to be very firmly in the classical SV-esque tech world, working on browsers or core infrastructural components for the internet, or web servers, or what not. Chrome bugs and heartbleed are the examples du jour.
The problem is that this is not the main area of use for C++. The dominant industries for C++ are finance, video games, and HPC, along with some more demanding programs on mobile, or (rarely) on desktop. People like you who lack domain knowledge about the main areas in which C++ are used, stomping their foot and insisting that memory problems are central and that Rust is valuable, does not really achieve anything.
Specifically, I think this work should be regarded as a "work in progress." :-)
I think you're point is completely correct though. You can write very safe code today using modern C++ features.
---
Reply to moomin:
The type system's intrinsic expressiveness limits the kinds of verification tricks you can “teach” it.
That's the primary reason Java took off. If you know C, you can write working C++ and Java programs straight away. For rust, we have to stop and relearn how to apply the logic (thus slow/no adoption).
And, I'm really sick of the constant HN sales pitches for rust. Make the transition easy and rust will stand on its own/sell itself (or not).
So now that even them have got the AOT memo, and Go and Swift also exist, I think the 2017's focus on productivity is very important.
The work done in the borrow check is incredibly and has already influenced decisions on the D, Swift and C++ communities.
Yet they all agree that they don't want to make it as hard as Rust.
Chris Lattner mentioned the feature in Swift should be exposed to expert programmers doing kernel and driver related programming.
I imagine Rust's image should be better than a language from experts for experts.
If the yardstick of “productivity” is how fast you can write incorrect programs, I'm not terribly interested in “productivity”. I'm sick of being a defensive user, having to refrain from probing how systems work in undocumented and likely unforeseen scenarios, lest I completely break them in unrecoverable ways.
I'd rather see an axiomatic semantics for unsafe Rust, so that I can formally prove that my unsafe Rust code doesn't break safe Rust's integrity guarantees.
> So now that even them have got the AOT memo, and Go and Swift also exist
Support for running in an unmanaged environment is a red herring. If you want to manage resources correctly, you have to care about ownership and lifetimes, regardless of whether you are doing systems programming (user programs manage resources too), need AOT compilation (JIT-compiled programs manage resources too) or need a GC-less environment (GC doesn't work for resources that need to be reclaimed eager and deterministically).
> I imagine Rust's image should be better than a language from experts for experts.
I have no idea. I'm not a member of any community (not a “Rustacean”, not a “Haskeller”, not anything), and I have no personal interest (financial or emotional) in any specific technology becoming popular. I only care about technical facts. Are facts for experts only?
Which is why usually there are other language or runtime features for deterministicaly managing resources besides just the GC.
The question is how to keep the way of Rust memory management, while e.g. making it easier to write cyclic graphs, or reducing even further the cases where one has to explicity type lifetimes.
Other than what Rust (and its research ancestors) uses, I haven't seen any alternative that isn't fundamentally broken in some way or another.
> making it easier to write cyclic graphs
I'd do it the same way I do it in any other language: initially create a DAG, then mutate it to create the cycles. This needs reference cells or something like that. Rust can do it already.
> reducing even further the cases where one has to explicity type lifetimes.
I don't see any way to do that in the vicinity of where Rust lies in the design space. What Rust has settled for (explicit lifetimes in type definitions, elision at use site) seems perfectly sensible to me. Of course, if a better alternative is found, that's a great thing.
> I'd rather see an axiomatic semantics for unsafe Rust
Luckily, this isn't an either-or! There are several groups of academics working with us on #2, while we work on #1. (among other things)
Good thing karma doesn't matter, and I have so much that a few comments being voted down doesn't affect me at all.
Criticizing it because C/C++ programmers can't just drop into it and start productively writing production-quality code is very short-sighted, IMO, and it's amazing how much it happens around here.