This is why it's time to move to Rust. Trying to retrofit single-ownership semantics to C++ never quite works. It almost works. But not quite.
C++ has been trying to get single-ownership semantics to work right for almost two decades. "auto_ptr" went through three standards revisions before the C++ standards committee finally gave up and dropped it. Now there's unique_ptr, where, after sprinkling on some move semantics, almost works, with fewer holes than auto_ptr.
Getting this right needs a borrow checker, like Rust's. Now that borrow checking has finally been figured out, there's a way out of this mess. Trying to do borrow checking via the type system just doesn't work out well.
> I doubt Firefox OS will ever allow for Rust based applications.
Sure it will. Compile them to Web Assembly first :)
People have already run Rust code through Emscripten. (It's not well supported at the moment, but the amount of work to be done to polish it up isn't large.)
> Until then it is just yet another programming language with an AOT compiler, whwre there is already a plethora to chose from.
Well, the borrow checker and safe manual memory management are unique features that allow moving a lot of code that previously had to be C++ for performance reasons to a safe language. (I've elaborated why safe dialects of Ada and such are not the same in previous posts.) I wouldn't characterize Rust as "just yet another systems programming language".
At least Rust has Servo and is being used in Firefox- while not really operating systems you would write Rust applications on, it's closer than many dead/dying systems languages have.
Another interesting route would be kernel modules- Rust could be a good language to write drivers in, if you need the extra security.
I don't think it necessarily needs a whole OS to itself just to survive.
Not just kernel modules—even userspace modules that are security-critical and used by lots of projects are good candidates for Rust. Think image decoders, media codecs, audio libraries, GL stacks, database engines, crypto libraries, etc.
>We should learn from history of systems programming languages. None of them survived without being tied to a specific OS.
How do you define systems programming? If you define it as writing operating systems then your statement is a bit of a tautology. If you define it as anything other than applications programming then I would say there are successful languages not tied to a specific OS, first and foremost Java.
Systems programming is the locus of development which spans boot firmware, operating system kernels, drivers, utilities and high performance middleware (API's to the operating system, numeric libraries/codecs, graphics, and whatever.)
Systems programming languages are characterized by requiring minimal run-time support, or supporting a version of themselves which is that way (e.g. ISO C has the concept of a "freestanding implementation", and there is a way for such a thing to be conforming).
As a rule of thumb, if it has no "freestanding implementation" or equivalent, then it's not a systems language.
I'm not aware that a flavor of Java exists with a toolchain that would let us rewrite U-Boot in Java, while keeping its footprint in the same ballpark. (I.e. the firmware image cannot contain an entire Java platform.)
People who write code in C++ are not doing this because it is fun or just because it is the latest buzzword. They do it in C++ because it is the language that supports the billions of lines of code in their industry, while providing a path forward through new standards and libraries. In summary, code written in C++ will not move to other languages because there is no need for that. The best opportunity for competitors is to write their own new systems and replicate what C++ already has. Not even Java has been able to do that up to this moment. And while it is possible, it will take a long time.
These are some good and valid points. Well, I write code in C++ because it's fun. And because I don't know any other language yet(!) that offers so many features to express my intends in code.
I'm _trying_ to learn Rust at the moment, but parts of the official book, as well as parts of Rust's standard library (function naming) have been horrible to read up to now.
Well I still use it (and Python) for fun for my own high-performance stuff - there's no other language out there (yet, without having to install custom compilers) that allows speed, flexibility and encapsulation...
Good points though - there are huge ecosystems out there in the games / graphics / VFX industries of C++ code and libraries, and I doubt any of it's going away any time soon.
It's actually reasonably easy, you just write a shim that can be linked as 'extern "C" {}' and then use jni. (I find it much easier than, for example, cython)
I usually use three patterns for memory management and they seem to work:
- copy by value for small datums
- store large datums in shared-ptrs if pointer access speed will not cause a bottleneck
- Relational model: I use large database objects for storing inter referencing objects - i.e. I don't use any pointer type for storing refences in large interlinked structures but handles such as indices or map keys.
I agree, trying to implement borrow checking or garbage collection 'quickly' through the type system in C++ usually leads to a world of pain.
Better designed languages offer poor intuition to C++ in this sense because the examples in those languages leverage the well designed type system. If one wants to do advanced stuff in C++ the correct way is to model that in the program code and not through clever constructor tweaks.
Vanilla "struct" data in members, plus reference pointers to data held caches of a database/memcached system are a pretty powerful way to work. You tend to not do assignment because making extra copies of stuff that's persistent has bad semantics; it will hurt a lot if you (say) have six copies of a Person and two or three pieces of code decide to do uncoordinated updates.
I'm not sure this a good chapter in that dialog. There are simpler (simpler in the sense 'less complected' and not easier) ways to handle resource management in C++ than this default ballet.
The example is clever C++ code. Most of the time clever C++ is bad because the stupid verbose way would be easier to understand and maintain. So, before demonstrating some intricate template scheme or inheritance trick there should be a good reason presented why it makes things simpler.
I agree on what you said but this example is a poor device if we are discussing code in a production setting.
Right. Several million lines of C++ says that we can't just "move to Rust," and a bunch of engineers doing C++ every day also means that someone starting something new in Rust will be looked upon with suspicion and worry, even if Rust will make the engineering staff stop hating life.
If you're claiming that the verbose way is just writing out the special functions, then I can assure you that it is not easier to maintain. If you are saying that the class in question could simply write a nested class that wrapped unique_ptr and had the correct copying behavior, maybe, there are arguments for both.
This example is fully intended to be solid code for production use, not primarily to be "clever".
I have nothing against the pure technical aspects of your article.
I originally answered to the poster who said this was a good example why Rust was better and my analysis was written from that point of view.
To me this was a poor comparison in the general sense since one can write production C++ using just copy-by-value, RAII semantics and stl containers and handling exceptions by letting the program terminate.
"If you're claiming that the verbose way is just writing out the special functions, then I can assure you that it is not easier to maintain."
I suppose I found this example lacking in the comparison-to-rust sense precisely because it invented this complected system that needed patching over. While real life systems often evolve through neglect and haste into piles of spaghetti this cannot be used as a direct evidence against a specific language in particular since it is more of a psychological and organizational rather than a language problem.
It actually works quite well. It's not without its quirks, but unique_ptr is really great. If you think there are holes in unique_ptr, then write an article with some good examples, I'll be the first to read it.
Rust hasn't seen enough widespread use to find all the problems and limitations with the language. The borrow checker seems great, sure. But looking at how Rust handles moves vs copies, I'm not convinced at all that it will be good enough in the long run.
This "mess" is caused by imbuing Example with correct move and copy semantics. In rust, it seems like most types have one or the other, but not both. If a type does not implement Copy, then when you try to copy it, it automatically gets moved from. So u = v would change the value of v. Ironically, that's similar to the behavior of auto_ptr that you just criticized, and quite unintuitive.
Note that while C++ zeroes on move, in Rust the compiler statically prevents you from using v after the move. The only way to use v afterwards is to move something new into it (which can only be done if v is mutable). Semantically, a move is a memcpy, except the source is inaccessible after the move.
Right, which is great, don't get me wrong. If you're going to have that behavior, having the compiler enforce it is good.
I just think it's a bit amusing, people complain about this sort of thing in C++ all the time. I can imagine if Rust takes over people will complain how you have no idea what u = v is doing unless you know what u and v are.
I'll admit my biases, but I prefer the c++ way: types know how to both move and copy themselves, and they will copy by default, but move when it's safe (rvalue) or explicitly asked to (std::move). I like looking at auto u = v and knowing that u is always a copy of v.
u = v is always the same thing in Rust: semantically a shallow byte copy ala memcpy (although there may not actually be a byte-by-byte copy at runtime, due to optimisations etc.). Whether a type moves or copies doesn't change this, it only influences whether v is usable (statically checked) after the assignment: the worst that happens if you don't know what type they are is a compile error talking about "use of moved value" (which also points to exactly the place that did the move).
The `Copy` trait (types that don't move) is just for types where memcpy is a valid & safe way to duplicate values of those types, it's not at all like C++'s custom copy constructors.
Well, if you have let u = v.clone(); then you are absolutely certain that u is a copy of v too (And it works on more types than just simple copying would).
Yes, of course you can do that, but you lose certain things. In C++ you can write code (e.g constructors) that will do the right thing with their arguments, move or copy, based on whether the input is an rvalue or not. Is there a way to get this behavior in rust?
In Rust, move semantics are assumed by default, and it's up to the caller to make a copy using .clone() if they want to keep ownership of the value (unless they know Copy is implemented and it's unnecessary, for example for integer types).
I know someone who writes his C++ like it is C; it is truly horrible to read and riddled with bugs. He does C-style casts from siblings in a class hierarchy, which in my mind is insane - they clearly are NOT castable in a safe way; only up the hierarchy is safe. No use of dynamic, reinterpret, static casts - all C-style. For loops are used everywhere instead of std algorithms. He doesn't see the point of references (pointers everywhere!) and const correctness is an expression he has never heard of, let alone abides by.
I realised the lack of C++ knowledge when he said "what is push_back???" with a vector. I can only imagine the brain implosions when lambdas replace all those stupid struct unary predicates I have to litter my code with, and when I implement move operators.
I am trying to convert my code to C++11 (mostly it is 03 style) which will necessitate a move in Windows compiler version but no big problem (and not too many changes - I use STL containers everywhere).
This isn't about any of that. Any classes you would apply this technique to already have the desired semantics. The rule of zero just argues that you should not write copy/move/destructor functions outside of dedicated resource mananging classes, so that you can write other classes with zero special member functions (other than the main constructors).
Really? Rust has problems of its own. For example, I don't like that it moves (on assign or parameter pass) by default (versus copying in C++). My feeling that you'd end up explicitly cloning a lot. Then, there are no exceptions, and I'm not going back to dealing with error codes. Also, code in Rust looks a lot noisier than in C++ due to an excessive use of sigils in the language.
I was initially interested in Rust, but my interest faded when I read a bunch of (official or semi-official) tutorial all of which seemed to focus on just one thing: types of pointers in Rust (how many? 5?) and borrowing semantics. Having not experienced any significant problems with ownership and resource management in C++, Rust didn't look like a worthy investment of time.
Your experiences with Rust might be a bit outdated. I'm guessing you're referring to http://words.steveklabnik.com/pointers-in-rust-a-guide. Rust only has `&T` now, in those terms. And things like try! give you a feel that's somewhat exception-like, even without actual exceptions.
You still may not like Rust anyway, but if you haven't given the language a fresh look since well before 1.0, a lot has changed, as your opinion may have too.
Yep, May 15. I like Rust, I'm slowly starting to port some of my code to it, but the amount of pre-1.0 hype it's received on HN is crazy. There's a ton of outdated information hanging around Google, there's a lot of lacking documentation, and there are still many many features which are not fully baked yet.
Relax. Give it some time. Unless if you've written a gigantic widely-used project in Rust, you probably shouldn't be telling other people to go do that at this point.
If you don't write a destructor, and the class needs one, the compiler will write one for you. Same with some of the other special functions.
The rule might be expressed as "try to write classes that are adequately served by the compiler-generated special functions" (which the compiler will write consistently).
If all your member objects have destructors, there should be nothing left to do. The generated destructor recurses over these and that is that. So writing a custom destructor is for cases when something additional needs to be done because some members don't clean themselves up, or there is some other issue even if they all do. The Rule of Zero seems to be saying that these cases tend to be code smell.
Anyway, of course you have to write (or at least declare) a destructor if you need a virtual one, at the bottom of a class hierarchy.
~MyAbstractBase() = 0;
The language-supplied destructor will not be virtual, and certainly not pure virtual.
You cannot just declare the destructor of a base class, since it will get called by the derived class destructors and linking will then fail. But you can write out an implementation for a pure virtual function, which will still keep the base class uninstantiable:
No, it will complain that A::foo is pure virtual, and that B must implement `foo`. However, if you define in B the method `void foo() { A::foo(); }`, it will work. In destructors this happens implicitly.
> "classes should not define any of the special functions (copy/move constructors/assignment, and the destructor) unless they are classes dedicated to resource management."
All C++ classes are dedicated to managing the memory resource that they are allocated on, am i missing something?
The rule is saying that a class which does explicit resource management should be doing only resource management and have no other behavior. In other words, only wrapper classes should manage the resource they wrap, and (ideally) no other resource management should exist. That keeps the management work out of the way of other classes.
And this leads to code that is either too complex, too inefficient, or both.
I mean it's fine if you are competing with Python or something ,but if you actually care about perf this is never going to work. But of course anyone wanting to coin a "Rule of X" doesn't want to see a problem in its full context, they just want to be able to pretend to have a solution well enough that they can at least fool themselves. Etc, etc.
C++ has been trying to get single-ownership semantics to work right for almost two decades. "auto_ptr" went through three standards revisions before the C++ standards committee finally gave up and dropped it. Now there's unique_ptr, where, after sprinkling on some move semantics, almost works, with fewer holes than auto_ptr.
Getting this right needs a borrow checker, like Rust's. Now that borrow checking has finally been figured out, there's a way out of this mess. Trying to do borrow checking via the type system just doesn't work out well.