1) every language has hoops to jump through to make them safe (e.g. garbage collection does not protect you from memory leaks) [1]
2) every 4-5 years or so someone comes out with a programming language/system* [2] (rust, go at the moment) that fixes it all and "encourages good practices" - and then the good practices change (e.g. microservices are making their second round. They're good for sysadmins/sres and very bad for programmers (because you have to serialize/deserialize everything and changes to the system propagate into potentially dozens of separate programs - and God help you if they're maintained by different departments)). I wonder what's next ? Data-oriented programming is the perfect solution for the problems of microservices, so that could be next I guess.
* they may call it programming language, they may call it a system. Of course, it's always both.
[1] https://www.youtube.com/watch?v=ydWFpcoYraU [2] Forth, C, COBOL (/mainframes), Pascal, C++, Oberon vs Modula-2, dBase (and it's competitors), Object Pascal/VB (the built-in database approach), Perl, Java, .Net, and now here Go/Rust
There's always a trade-off between performance and security, but with better tooling support the price for secure code will be very low. Right now security is bad because we look at security as a feature. As something that is added to a project, instead of as something that guides the entire design.
Your suggestion that static analysis can fix this is wildly optimistic, because C/C++ code for a modern browser uses memory pools, pointer arithmetic, just-in-time compilation, and shared memory between object instances, threads and processes and so on. It's hugely complex, and correctness can't be verified with some sort of static analyzer or with added runtime checks.
Wrong. You can write perfectly safe code in C++. You seem to have this ridiculous idea that pointer arithmetic is mandatory on C++. "Pointer arithmetic" is not a feature of C++. It's a feature of how computers work. C++ allows you to do it if you want. Memory pools are not a C++ feature, memory pools are part of how a modern computer and OS works. You know, memory doesn't appear magically in your program.
> Rust forces programmers to indicate ownership and lifetime of memory
So like C++ with unique_ptr, shared_ptr, and RAII?
Also I wanted to say that some of you keep mixing C and C++, calling them "C/C++". They're two different languages.
Always written C++ taking advantage of the language features that allow for safer code and bring it closer to Ada linage than C.
Never saw anyone else doing the same in the enterprise codebases I worked on. My team at CERN was the only exception.
However C++ with teams that haven't drink the safety kool aid, becomes C with a C++ compiler quite fast.
Hence why nowadays I tend to only use it occasionally in side projects.
> So like C++ with unique_ptr, shared_ptr, and RAII?
It is like that, but with stronger guarantees and less overhead.Smart pointers in C++ are very slim, which overhead are you referring to? And which are the stronger guarantees?
Additionally, in C++ there's nothing stopping me from wrapping a unique_ptr around a pointer that's aliased else where. This violates the guarantees of a unique_ptr and forces the user to make sure its invariants are held. In Rust the compiler ensures that your Box (Rust's unique_ptr) can never be owned more than once.
In which case would you like to enforce a thread unsafe version?
>Additionally, in C++ there's nothing stopping me from wrapping a unique_ptr around a pointer that's aliased else where. This violates the guarantees of a unique_ptr and forces the user to make sure its invariants are held. In Rust the compiler ensures that your Box (Rust's unique_ptr) can never be owned more than once.
True, nothing stops you to do that and it is nice to know it on compile time. But I fail to say how you would do this by accident. If you deliberately want to do this kind of things, then you are asking for trouble and it doesn't matter how awesome your language is, you would find a way to shoot your foot.
> In which case would you like to enforce a thread unsafe version?
Any time you don't use shared_ptr in a multithreaded context, you're paying extra for the overhead of atomics. In Rust, if you need shared ownership, but you're not doing anything with threads, you can remove that overhead. If you then later try to use it across threads, the compiler won't let you, until you switch to using Arc.If I am following you well, then what you are saying is that you if you are in a single thread application you don't need to use a shared_ptr equivalent. Wouldn't that mean that you are using a garbage collected pointer? (Which should have a greater overhead than a shared pointer) because otherwise you would be using plain old raw pointers (with all its problems).
> you don't need to use a shared_ptr equivalent
Well, it's the same thing, but without the atmoics. Rc and Arc only differ in the instructions used to update the count, Rc uses regular increment, Arc uses atomic increment. The code is otherwise the same.For stronger guarantees, I was going to point out that if you std::move a uniq_ptr, it becomes null, but Rust prevents using a Box<T> that's been moved at compile time. Your example is good here too.
So you are saying that the lack of move constructors (hence copying the structure) will make Vec<T> faster than std::vector? I don't follow that logic, wouldn't it actually be slower?
> I was going to point out that if you std::move a uniq_ptr, it becomes null
The original becames null because the semantic is actually tranfering ownership which might be desirable depending on the case. Also you need it in case you are using custom deleters.
http://stackoverflow.com/questions/13860219/what-are-the-dif...
> I don't follow that logic,
Specifically, I mean that when the vector changes size, the vector needs to reallocate. In Rust, since there are no move constructors, it's just a memcpy of the whole chunk of memory, one big one. Very straightforward and fast. In C++, you need to call all those move constructors. > The original becames null because the semantic is actually tranfering ownership
Absolutely. In Rust, this is tracked at compile time, and attempting to use after the move is a compile-time error. In C++, you'll get a null pointer dereference. (I _think_ that technically it's in an 'unspecified' state, not a null one, but on my system, null is what I get).> In Rust, since there are no move constructors, it's just a memcpy.
memcpy is actually slower that move the ownership of a buffer, that's why you avoid copying as much as you can (std::vector also uses memcpy inside).
> In Rust, this is tracked at compile time, and attempting to use after the move is a compile-time error.
It is nice to get a misuse at compile time, but my point is that once you transfer the ownership of the pointer you shouldn't be using that unique_ptr anymore. I'm probably biased but I don't see this "accidental" misuse happening often (tbh I haven't seen it ever and I have seen some horrible things in C++ before)
> (std::vector also uses memcpy inside).
I don't think I am communicating myself clearly, so I'll just bow out about this issue, until I have an example ready. I should write one anyway, I just don't have it at the moment. > I'm probably biased but I don't see
this "accidental" misuse happening
> oftenWhile that may be true, as the article points out, all it takes is for someone, anywhere, to slip up once, and then you possibly have a serious security violation. Having the compiler double-check your work is nice.
You can't write safe code in any Turing complete language. That's the whole point of Turing completeness. The only reason why memory attacks are as common as they are is because C like languages are the most popular ones.
If we replaced everything written with C to a "safe" language like, I don't know Haskell?, we'd have just as many zero day exploits of the monads within programs.
If we replaced everything in C with Haskell, we'd have an entirely different problem. The attack surface wouldn't involve buffer overflows and stack smashing, it would involve various DoS attacks. Those might be easier to address though.
Javascript is turing complete but it's trivial to write a (slow) javascript interpreter in Python that allows anybody to run any javascript program without any risk to their machine. No memory attacks possible. To privilege escalation possible. No unchecked stack overflows. The system would be sandboxed and secure. It will just work or fail gracefully.
It's only when we increase the complexity of our runtimes a thousand fold and when we cut corners to squeeze out higher performance that all the nasty vulnerabilities start to creep in.
I disagree. There will always be security issues, due to (ab)uses that developers didn't consider, but there can be different probabilities. Requiring developers to learn about language gotchas, remember those gotchas when coding, and jump through hoops to avoid them, just stacks the odds against us.
Correct. But, a lot of code is written in Turing complete languages which does not really require any Turing completeness at all. And some code should be implemented in non-Turing-complete, verifiable languages.
This is in fact where the newest academic languages are going. In dependantly typed languages bounds checks are only done in theory. you have to include a proof that your indexes are within bounds, and the compiler verifies that proof. If it checks out, it compiles. If not, back you go. Far safer than java/go and the like and far faster than c++. These languages tend to allow pointer arithmetic too, for beating it in speed is almost impossible.
Then you've picked the wrong profession. You write that like it was a fact, while it's obviously nothing more than a lack of skill (and maybe knowledge) on your side.
If I chose a language primarily because it's 'safe', that would mean that either I'm pretty bad at writing safe code, or that I'm just unnecessarily lazy.
C and C++ have long proven that with average skills teams, the end result is lurking buffer exploits, dangling pointers, corrupted memory somewhere in the code base, that usually take days to sort out.
This really cuts to the core of ideological differences between C/C++ and Rust camps. C/C++ programmers tell the compiler trust me, while Rust programmers say check me. I can't help but feel to a degree the Rust ideology is superior. To make by bias clear I work in C, and write in Rust in my spare time.
Safer systems programming languages were already available outside AT&T when C was born. Rust is just the latest addition to them.
On the server: unikernels. On the client: new mobile platforms, like OSes based mostly on web technologies (which while not quite being there yet are getting more and more powerful).
C/C++ is not only a poor choice in regards to security, but also to develop for hardware with hundreds or thousands of cores, and all hardware platforms are moving in that direction. Yes there are of course ways to deal with that in C/C++, but other languages are much better at this.
Within just a decade or two I can see a fundamental shift away from the old computing paradigm, at least for the majority of systems in mainstream use.
iOS slowly going Swift (Very few WWDC 2015 presentations were Objective-C)
WP 7 used only .NET. WP 8/10 use .NET and C++/CX.
Android uses essentially Java and Google only allows a very constrained use of C and C++ in their NDK.
So even though C and C++ are on the kernel and lower platform layers, at least the user space use is lower than on desktop platforms.
The correctness invariants of complex C++ programs (such as browsers and JITs) cannot be 'discovered' by static analysis - they must be, at least in part, supplied by the programmer.
C and C++ were not designed to allow programmers to specify such invariants (and have them automatically checked). I am not convinced that introducing them can be done in a clean way.
In French, your first line would be, "Oh, hell no!"
It's not the language, it's the tools.
Add a static analyzer pass to each C/C++ compiler which is switched on by default. Add clang-address-sanitizer-style code when compiling in debug mode, and also offer runtime checks as option for release-compiled code. Both combined would have caught 99% of memory safety issues that pop up now and then.
If necessary extend the language with optional ownership keywords a'la Rust, and allow me to switch off features I don't require to reduce complexity.
Most important of all: provide options that I don't need to pay at runtime for memory safety with precious CPU cycles.