I'm bored of all the Rust vs. C++ comparisons, when the C++ code is something you simply don't write with modern and idiomatic C++11 and C++14. Not that you could not write it, but seeing this would not pass me reviewing the code.
The add_one example, as simple as it is, is a horrible example.
Why not embrace value semantics and write it like:
int add_one(int x) { return x + 1; }
There is simply no need for pointers or references. Write simple code first. To the heap allocation example:
auto x = std::make_unique<int>(5);
Even if this is a silly example, just say no to unnecessary heap allocation. Why not:
int x{5};
Now to the lifted example:
void add_one(std::unique_ptr<int> num) {
* num += 1;
}
This mixes operations with lifetime management. The add_one function does not need to care about ownership. And it does not need to take ownership of its argument. Instead on the call site do:
int add_one(int); // from above
auto p = make_unique(5);
auto six = add_one(*p); // <-
There are valid points in the blog post and I know that most snippets are meant as examples. But as someone writing C++14 you can't win me with those kinds of posts, even though I'm having a fun time playing with Rust in my spare time.
You seem to be pretty fundamentally missing the point of using those examples.
All of your examples look almost identical in Rust and C++. That's not a positive, nor is it a demonstration of similarity - it's a useless set of examples for a post _demonstrating the differences_ between the two. :P
It's worth looking at each of the examples in the article and thinking about them in the context of the more complex practical problem that do _require_ mutable/heap state.
Again, think of the examples as 'C-Reduce'd[1] ones - just enough for an experienced programmer to 'get' the differences and infer its relevance to real world cases.
To give at least one motivating example: for your first case (functional add one) - that's entirely passable for the (tiny) int, but quite useless for the larger case of BigInteger. That's where Rust's default move semantics would really shine, but using BigInteger as an example would introduce lots of irrelevant semantics, making the example bigger but scarcely more informative.
I can appreciate that a lot of these examples are meant in the spirit of C-reduce, but to me these examples use trivially-bad C++ code to illustrate the complexity of dealing with more difficult ownership scenarios in C++, while glossing over how awful those are in Rust as well.
Where are the Rust explicit lifetimes in this example? If the C++ code has been made intentionally obtuse with unneeded measures here, why aren't Rust lifetimes (which I think are a terrible wart on the Rust language but are absolutely necessary for non-trivial ownership scenarios in Rust) captured in this example?
> Where are the Rust explicit lifetimes in this example?
Rust has a feature called 'lifetime elision.' Three rules, that removed the need for virtually all (iirc, something lie 95%) of explicit annotations in the compiler.
The first rule is the one that applies here:
fn foo<'a>(x: &'a bar) {
can be written
fn foo(x: &bar) {
That is, if there is only one lifetime annotation, it may be elided. The annotation wouldn't do anything special in this case, and so is just boilerplate.
I know about lifetime elision. I originally got into Rust based on my knowledge of ARC in objective-C, my love of type-inferencing, and blog articles like this one, promising static compile-time management of object lifetimes.
The point that I'm making is that learning about the necessity of explicit lifetime management in Rust, and the awkward syntax supporting it, was, for me, a splash of cold water in the face. I think that there are a lot of C and C++ programmers who read about Rust and read articles like this and get similarly excited about the prospect of static object lifetime management, but ask themselves whether it's really possible to completely hand lifetime management over to the compiler. And of course it isn't - sometimes you still need to concern yourself with the vulgar details of managing object destruction, and Rust's syntax for that is kind of ugly.
Blog articles like this are deceptive. Why are we exposing C++'s warts for the purposes of demonstration (and others here are right - the examples here are contrived) without mentioning something as ugly as explicit lifetimes in Rust? To make a point? What is that point?
I believe your parent post was alleging that the C++ code is deliberately unidiomatic, and thus saying that the comparison would be more apt if the Rust code was also made unidiomatic via the use of lifetime annotations that would usually be elided.
I see these posts as pedagogical, not advocacy. That is, if they were advocacy, it would be relevant to criticize how realistic the C++ code is. But if they are pedagogical, then that matters less, and the C++ code is just a way to teach the reader how something they are unfamiliar with (Rust) maps to something they are familiar with (C++).
is an awful function (really no checks of any kind, such as multiple holders). You suggest that it's valid for more complex practical examples, but that's a simple example where you'd probably want:
void add_one(std::unique_ptr<BigInteger> num)
BigInteger is a common example often written in fluent/mutable style or to take ownership of the argument in just this way.
Your second function still mixes ownership with resource management. Taking an unique_ptr<BigInteger> by value means, add_one takes the ownership of it. But it does not return anything, making it a useless function.
The though-process goes like this:
* Do you need read-only access to the argument, take a const T&
* Do you need to modify the argument, take a T (by value), letting the call site decide to either pass an rvalue (move it, no copy), or an lvalue (copy)
There simply is no need for unique_ptr<BigInteger>, as BigInteger already handles its resources internally (with move semantics).
It's the same reason as to why an owning pointer to a vector is silly when a vector already handles ownership of its resources.
Yup, I agree with you (and pjmlp, who I can't reply to) on all of this.
I can see where you're coming from in pushing for all-value semantics, taking advantage of the mechanisms built into the language to control how those semantics play out.
I think it's worth remembering through all of this that getting just the right semantics requires a little more effort and thought on the part of the callee, in C++-land, as well as which the relative lack of consistency (and opaqueness) in these semantics, at least in the absence of a full IDE or similar to jump to signatures.
Oh and briefly, the C++ may be unidiomatic, but it's perhaps useful as a C++ mimicry of the Rust code to show maximally similar semantics?
I think perhaps the dismissive nature of your GP post blinded me to exactly what standard you wanted the C++ to hold to (oh and also, swapping out heap allocation still seems to be missing the point, as there are definitely cases where that's the 'right' behaviour)
Another way to formulate these kinds of blog posts would be to take real world, open source, modern C++ code and convert small parts of it to idiomatic Rust code. Then, the author could compare/contrast the advantages of the approaches there without having to guess or infer if the example is really relevant to the real world.
There is always a tension between making examples easily comprehensible and making them realistic. People often use references and pointers to integers as examples because they have (arguably?) the lowest conceptual overhead to distract from the point of the article. So I think you make good points, but that a more charitable reading of the examples would substitute "some type I would realistically put on the heap and make references to" everywhere you see "int".
But in general, I think some of the advantages of Rust boil down to "compiler enforcement of stuff that good practitioners of modern C++ already do". And that's a good thing.
> I'm bored of all the Rust vs. C++ comparisons, when the C++ code is something you simply don't write with modern and idiomatic C++11 and C++14. Not that you could not write it, but seeing this would not pass me reviewing the code.
Seeing last year's CPPCon videos was illuminating to me.
A big problem is that many are still writing pre-C++98 style or chained to style guides that prevent many of the C++11/C++14 improvements.
Specially problematic when IT is the one calling the shots to what is available in the development environments.
I'm bored of C++ apologists pretending like there's really such a thing as 'modern and idiomatic C++11/14' that any two C++ programmers actually agree on, or that you'd ever actually see practiced consistently in the wild.
You can no-true-Scotsman code that compiles, runs, and blows up in your face with any modern C++ compiler all you want, it doesn't change the fact that Rust eliminates entire classes of errors that are trivial to hit in C++.
I am looking forward to the day Rust is available out of the box in XCode, Visual Studio, Android, QtCreator.
Until then, it is going to be "Modern" C++ for the lower layers/common code, with the platform vendor languages for the upper layers, regardless how Rust improves over C++.
I am a big fan of the type safety of Pascal and ML language families, but eco-systems have more weight than just the language.
I think you make an excellent point that this is a poor demonstration if one is trying to convince a decent C++ programmer to try Rust. Many of these issues are simply not a problem for moderately disciplined C++ programmers.
If anything, this sort of post is more convincing to a junior developer who wants their compilers to do more to warn them when they've done something stupid. They want to write idiomatic C++, but they're lazy and inexperienced, so if there's a language where the compiler provides feedback when they deviate from what's idiomatic, they're going to opt for the feedback.
I'd be very interested in what's drawn you into trying Rust and what you think are the most redeemable qualities.
I'm trying Rust for the same reason why I'm investing weeks into learning Clojure. Because I'm convinced learning more approaches to high-level problems (like ownership, concurrency, typing, patterns) is one way to improve my overall skill set and thinking.
One thing I like about Rust is how you e.g. can specify a Sortable trait and then with T: Sortable require it for types.
This will eventually come to C++ with Concepts (lite), for now there are ways to work around this, e.g. with:
Without a feature like this you may see cryptic errors from inside template instantiations coming from your stdlib, when you try to sort something that is not sortable.
I agree completely. None of these examples are going to convince a C++ programmer who's already sunken the cost of learning the language to switch to rust.
Something I realized at some point is that these "language A vs language B" comparisons aren't really there to convince adherents of language B to switch to language A. You'll see lots of comparisons on the web of Haskell/Scheme/Lisp/C#/F#/Ocaml etc put up against really poorly written C/C++ code, with "language A" inevitably coming out of the comparison pretty well.
Thinking back to my days at school, a lot of physicists were telling each other that if they wanted their simulations to run in a reasonable amount of time, they had to code them in C. These comparisons are really aimed at them - the message here is, "yes, a really great C programmer could write machine-optimal FFTs or whatever in C, but the code that you, a beginner, will end up writing could actually end up being not much faster and in some cases perhaps even a lot slower than the equivalent code in Haskell/etc".
> If anything, this sort of post is more convincing to a junior developer who wants their compilers to do more to warn them when they've done something stupid. They want to write idiomatic C++, but they're lazy and inexperienced, so if there's a language where the compiler provides feedback when they deviate from what's idiomatic, they're going to opt for the feedback.
Compile time checking attracts more than just the lazy and inexperienced.
(Maybe you didn't mean to imply otherwise, but it sure as hell reads that way.)
I didn't. I was describing what makes it attractive to newer developers with limited C++ experience. I'm sure it's also attractive to some experienced C++ developers who have grown tired of C++'s shit.
I personally find C++ is fine and dandy if you're working with a disciplined team. On the other hand, it's bad qualities are immensely magnified when the overall team lacks in talent and/or experience.
I'm always surprised when programmers confuse unidirectional implication with bidirectional implication. I'm only making a comment that alternatives to C++ are really good at attracting people who never developed the discipline required to write good C++. Experienced and talented C++ developers don't really have C++ problems, so most articles trying to lure them away with promises of memory safety using contrived examples will fall on deaf ears.
I know many good and experienced programmers with former C++ experience who detest it. Personally speaking, I'm an average but fairly experienced programmer, so I tend to prefer tools that help me catch my mistakes so I can focus on solving problems cleanly.
Experienced and talented C++ developers have C++ problems all the time. Or maybe these core dumps I deal with in my day job are all in my imagination...
Yes, you can also write safe code in an unsafe language, but until you are literally willing to code review every program anyone writes in your unsafe language of choice, there is value in having safe languages where we don't have to rely on your throughput and accuracy.
There is a real difference between a safe language, and a safe subset of an unsafe language that gets verified by some guy (no disrespect). The former is (everything else being equal) just way better for everyone involved, especially for the guy.
I think you missed the point. Of course you'd never implement add_one that way, but at some point you'll have a function where you want to pass a unique_ptr as an argument, and then your code will happily continue to compile even if you use the pointer after the move.
Accessing a moved-from object is one of the examples I didn't criticize because it could be an issue and I hope for compilers warning about it. That doesn't make my other claims invalid, though.
> Not that you could not write it, but seeing this would not pass me reviewing the code.
hear, hear... Few of my recent features were passing through 7 layers owned by 4 teams (C++) with one team also having Java subteam (who seem to have never heard about Java coding style, etc...) Each team has a person who like you is enforcing his the _only_ right way ... which happen to be different from each other. Well, i just code to whatever style that module is written in :) After 25 years in the industry i've seen a lot of "right ways"...
>The add_one example, as simple as it is, is a horrible example.
>Why not embrace value semantics and write it like:
int add_one(int x) { return x + 1; }
>There is simply no need for pointers or references. Write simple code first.
yea, man. That particular style is there too. Slow as hell. Works for the teams of really average programmers who can't grasp and keep together in mind more than one simple aspect of a small piece of functionality at a time.
It's pretty amazing how detailed and helpful Rust's error messages are.
rust_dangling.rs:6:5: 6:6 error: cannot borrow `v` as mutable because it is also borrowed as immutable
rust_dangling.rs:6 v.push(6);
^
rust_dangling.rs:4:14: 4:15 note: previous borrow of `v` occurs here; the immutable borrow prevents subsequent moves or mutable borrows of `v` until the borrow ends
rust_dangling.rs:4 let x = &v[0];
^
rust_dangling.rs:8:2: 8:2 note: previous borrow ends here
I really love Rust's error messages, but I do wish I could get some more arbitrary code inspection prior to the compilation step.
For example, sometimes you may declare something with some odd syntax and Rust effectively say "I was expecting type `A`, but this is type `B`." Frequently, I want to have a little more insight into what's going on, but the inability to maybe write some code that will print out information about something causing a compile error is problematic.
That's the thing about Rust: because the burden of debugging is so shifted to the compilation step, Rust absolutely HAS to have good error messages because that's all you have to work with.
However, I feel this shift in workflow can be overcome with different tooling, and I suspect this is mostly a symptom of being new to the language.
you got that right, I wish all languages did that,and why not, even suggest modifications to the code in order for it to compile or run... Errors should be an opportunity for programmers to learn, it shouldn't feel like a punishment. But we'll get there eventually.
I write compilers as a hobby. Providing good error messages is incredibly difficult—the compiler has a ton of information at hand, but it’s hard to know what’s actually useful to present, largely because you can only guess at the programmer’s actual intent.
Obviously not so much for the kind of memory errors the GP is talking about, but clang/clang++ do provide similarly detailed error messages, including suggestions for name-related errors as to what you might have meant (either because a different variable provides the member you specified or becuase of a similarly spelled name).
clang was what I based the current Rust compiler's error messages on. Matching its quality was a design goal from the start, since I think that one of the biggest reasons for clang's success was the quality of its error messages. Diagnostics are the UX of a compiler, and so they're deserving of the exact same care that is put into mobile app or Web design.
Yes, absolutely. I have a theory that a big part of the reason clang leapfrogged g++ so quickly on these things is because it was (initially aiming to) dogfood itself, So getting better error messages than g++ could give was an early and high priority task to its first users.
> It's pretty amazing how detailed and helpful Rust's error messages are.
The quality of the error messages is a property of the compiler, not of the language. But of course, a well-designed language also provides the abstractions that ultimately can lead to good error messages.
The author mentions that Rust's "Box type is an owning type" and compares Box to C++'s std::unique_ptr. Worth noting: Rust's most basic variable syntax provides unique_ptr-like behavior, so you usually don't need the Box type for this behavior. For example:
The default mode in Rust is value semantics, similar to C++'s default. Rust does have the advantage of move-by-default on uses and checking the validity of moves, but if you want unique_ptr behavior then you need Box. C++ has the advantage (if you would call it that) of allowing arbitrary code in move constructors.
I think he was more talking about the unique ownership part, especially that values themselves follow the same rules.
Which I find fitting, since in Rust, the Box is (at least to me) more about indirection. The single-owner semantics just come in because Box follows the same rules as anything else, while for unique_ptr they are part of the pointer concept.
Are you sure your comments are right? It seems to me like the value would be memcpyed, when you pass by value. There is not one unit of memory that is "taken over," or if there was, then Rust would have a serious problem.
It might be a terminology thing. Assuming MyStruct only contains values, it will fully live on my_func's stack and nothing needs deallocating on drop. When take_ownership is called a copy of it's value would be passed in, and the original location marked unusable (assuming MyStruct doesn't implement Copy). So yeah, there's isn't any specific memory location being taken over, since there's two locations involved.
> It seems to me like the value would be memcpyed, when you pass by value.
As far as I know, the compiler should not copy in that instance. Rather, it should move.
> There is not one unit of memory that is "taken over," or if there was, then Rust would have a serious problem.
Could you elaborate on that? Rust does have an ownership model, and ownership can be transferred as in the example. What sort of problems would you expect that to cause? If you're worried that it will invalidate existing pointers, the compiler checks that for you. Unless you deliberately circumvent the check, the compiler guarantees that your pointers are valid.
Semantically speaking, the only difference between a move and a copy is that you're allowed to use a copy type afterwards, and you're not allowed to use a move type afterwards. It's still a memcpy. Of course, these may be elided by optimization passes.
> Semantically speaking, the only difference between a move and a copy is that you're allowed to use a copy type afterwards
Are you speaking about Rust specifically, or move in general? I had always understood that move was no more expensive than passing by reference. That is, I had thought the memory was on the heap and didn't need to be copied each time someone new took ownership of that heap space.
> That is, I had thought the memory was on the heap
An example:
let x = Box::new(5);
let y = x;
While the 5 is allocated on the heap, when we move x to y, _the pointer itself_ is memcpy'd. That's why Box<T> isn't Copy; as you say, a simple memcpy won't actually duplicate the structure. Make sense?
(and in this case, I'd assume llvm's optimizations would realize the copy is superflous and just elide it, but semantically, that's what's up)
If that's just a struct, it's stack allocated. So it's not ok the heap in the first place. IIRC, LLVM may optimize passing it to the function by reference though.
Good to know. Thanks! So I guess the moral is, if you have a big, expensive struct, make sure the expensive part is in a sub-structure you know is heap allocated, such as a Vec. E.g.:
That is not a big expensive struct (to memcpy). (edit: I'm just bickering about what to call things, what you say is right.)
Your intuition about the performance of this sort of thing might be served by reading about how calling conventions work. You might not need to copy the struct from a local variable to where it belongs on the stack or in registers when calling a function, either because the calling convention says you put a pointer to the struct somewhere (depending on its size) or because you're (you being a compiler) clever enough to put it in the place that'll end up in the argument list later. The callee, however, has less flexibility, and if it needs to pass the value somewhere else, it'll probably have to copy the data. This is way better than allocating it on the heap -- stuff on the stack is in the L1 cache, you compute it's location statically instead of having to traverse data structures, but yeah if you found yourself copying around a 1000-byte struct you might want to box it or pass it by reference. I only know about C and C++ calling conventions though, so don't infer from my comment that Rust isn't doing anything "special" for big non-copyable structs -- I wouldn't know.
Rust (well, LLVM really) will automatically pass any "large" struct by reference, which in practice is going to be anything more than a few words (I think in the current implementation, it might actually be two). Unless the function call is inlined, of course, in which case LLVM can do pretty much whatever it wants.
Well, it depends. I mean, generally, you want to give flexibility to your users. Maybe I _do_ want to stack allocate an ExpensiveStruct for some reason. You can always Box the struct to have it on the heap.
Yes, I had understood the struct's memory to be on the heap. My thinking was, keeping the memory on the heap allows for inexpensive moves. Whereas, the pointer to the heap space may indeed be on the stack, as far as I know.
But I could be mistaken. This is all based on hearsay--just stuff I've read about the Rust compiler. I don't actually work on the compiler myself.
The Rust documentation on ownership shows examples of function with reference parameters and Box parameters, but nothing like the OP's `fn take_ownership(obj: MyStruct)` example. Is that an oversight in this page in Rust docs?
I think it is an oversight: Box isn't particularly special with how it handles ownership, but people do get the wrong impression that Box is the way to have uniquely owned data, whereas it is actually the default. This happens less now that Box doesn't have special syntax, but I agree with you that we may be able to improve those docs in this respect.
Unfortunately, it can be hard to have an example that's simple enough to understand, while still emphasising the right concepts. Hopefully we can find something.
> nothing like the OP's `fn take_ownership(obj: MyStruct)` example.
It uses Box to demonstrate ownership in the first section, but that works the same way with any Rust type unless said type was opted into copy semantics, there's nothing special or magical about Box (at least in that respect), it's just a wrapper for a raw pointer.
Those trivial examples seem to be mostly to not scare off C++ programmers. It's more interesting, though, to see how far you can push single ownership. You can build a single-ownership tree. Rust lacks a "nil", but has discriminated variant records (called "enums") to handle has/doesn't have semantics. You can also grow a <Vec> of owning references, so you can have an array which owns a variable number of children. Using all those features together, you can build a tree. Take a look at the XML library for Rust to see how this all works.
But it can't have backpointers. That would violate single-ownership. Weak pointers for backpointers are available, but that's an unstable feature and introduces reference counting overhead. Rust needs some construct which includes a single-ownership forward pointer and a dependent back pointer, updated together so that they're always in sync.
Rust has the concept of a possibly-null pointer; you just have to write it explicitly by wrapping something in the Option type. In particular, Rust has a guaranteed optimization that says if you have a type with a single zero-argument constructor and a one-argument constructor, and the one-argument constructor takes a type that can't be null, then the type with those two constructors will just use null to represent the zero-argument constructor. So Option on a pointer/reference type just represents a nullable pointer.
You can always implement that sort of thing with `unsafe` blocks. Since any use of raw, non-smart pointers in C++ counts as unsafe, I think it'd be pretty okay to write a couple of helper functions to maintain that invariant, and then call those functions from safe code.
But yeah, that probably ought to end up in either the standard library or a very common third-party library, at some point.
To do that right, you need help from the borrow checker. When manipulating an owner pointer with a backlink, you'd need the backpointer to be unborrowed at the moment the ownership changes. A feature like this needs compile time checking.
I really need to think this through and make a proposal for Rust 2.
To me, C++ suffers a lot from the "spoonful of sewage syndrome". Yes, C++14 is a lot prettier than C++, but they preferred to be backward compatible with a rotting language. If people know that some things are bad, but prefer to retain them (for whatever reason), then they should not complain that at some point somebody comes along and just drops the insanity.
(yes, C++ would be a lot better (or less worse) if they would correct mistakes from the past).
Anyway, Rust is simply a language with a lot less cruft, combined with a better tool chain (compare rust compiler errors to g++ compiler errors). That's not a surprise, considered C++'s age (30+ years).
OTOH, without C++, Rust would have been impossible.
OTOH2, Rust wanted to be somewhere between C++ and OCaml.
I think they succeeded.
C++ isn't full of cruft, it's full of features. Many of those features apply only to a small niche, but those all those small niches add up to become an audience.
Much of the "cruft" of C++ stems from the template system. Rust has very sensibly decided not to go down that path. But aren't you happy that one language has sacrificed itself so that we can express ideas like this...
template<typename T, size_t A, size_t B, size_t C>
Mat<T, A, C> matmul(const Mat<T, A, B>& left, const Mat<T, B, C>& right) {
...
The actually useless features like exported templates and trigraphs, are removed.
Nitpick: Comparing Rust's error messages and g++'s error messages, and saying that therefore Rust has a better toolchain, isn't really fair. pcwalton mentioned elsewhere in the thread that Rust's messages are based on clang's, which are orders of magnitude better than g++'s.
C++11/14 should have been a clearer break from legacy ANSI C++. Overloading of keywords just to avoid adding new keywords to the grammar (lexer/parser) - that's what makes C++ really ugly. Especially if one has to read through old third party code. The STL was so slow/bad that almost ever PC game shipped with their own implementation. (I heard STL implementations got better). Boost is too bloated, the compile time and executable increases and many avoid adding it to their project.
But adding new keywords instead of reusing/overloading old ones for newer features would have been nice. Writing new code in C++11 isn't the problem, reading old ugly C++ code and trying to understand it is. I think the clean break is Rust and it already looks promising. Sure some of its syntax is influenced by non-C languages and looks a bit weird, but I can live with that.
Operating systems today are mainly C based (majority of kernel mode code) - WinNT/ReactOS, Unix/Linux/*BSD/OSX/Android. Also in the user mode side, C is heavily used in all above mentioned OS (WinAPI, DirectX, etc. are all in C). C++ is also heavily used (e.g. for GUI applications like Office and newer OS development). Most third party applications are written in C++ inclusive almost video games of the last 15 years. Apple bought Steve Jobs' NeXT Software and with it NeXTSTEP (now MacOSX) and Object-C.
I read somewhere that Microsoft is working on a Rust competitor and Swift with it's great syntax is refreshing. But with these closed languages, I hope that Rust has success.
Ada is great and heavily used in specific domains - only its syntax is not that great. Also VHDL is based on Ada and its syntax. I had a Professor who had worked on Modula&Oberon with Niklaus Wirth, though he later moved on to Sun Java.
Genode, BeOS, Symbian and OS/400 are a few examples of OSes developed in C++.
> C is heavily used in all above mentioned OS (WinAPI, DirectX, etc. are all in C)
DirectX is COM and most new WinAPI since Vista are also COM.
No one in his right mind uses COM from C instead of C++.
Windows is moving into straight C++ as of Windows 8, with the adoption of kernel support for C++ based drivers and the move into WinRT, a COM superset coupled with .NET metadata.
The upcoming Windows 10, embraces WinRT for its Universal API model and C style is considered legacy. Hence why Microsoft is not caring to update their C support beyond what is required by the ANSI C++ standard.
Good luck coding COM and WinRT applications with pure C.
Mac OS X drivers are written in a C++ subset, Embedded C++.
Now imagine, if it has taken this long for C++ to start replacing C in core OS development, how long it will take for Rust to fill in?
Rust is nice, but I am too long in this industry and have seen too many languages come and go in systems programming. The only ones that stayed around have had OS vendors support in their respective SDKs.
I wrote "operating systems today are mainly C based" and that's true. Also WinNT/ReactOS was inspired mainly by VMS and OS/2. I am well aware that many other OS exist.
> DirectX is COM and most new WinAPI since Vista are also COM
Fundamentally DirectX and the (post Win95a) shell is COM-based API, and while COM may look on the surface as if it is C++ based, it's actually not - a COM interface isn't a class, it's a struct, and full C compatibility exists (COM as-designed is intended to be language-neutral). And major parts were coded in C. I suggest checking out the Wikipedia article on COM for further info. Coding against COM from C may not be as simple as from C++, but that doesn't matter. dotNet and their new WinRunTime are also COM-based. Sure, it's clear that MS is using C++ nowadays and nothing is wrong with that. The real question is what the future of C#? Will it be 'replaced' by a new language similar to Swift and Rust? Or maybe it will get a static compilation option similar to Go-lang that contains the GS and parts of the framework in the executable. Microsoft has little C# based software on the client side - due to the massive setback and fail of the original "Longhorn" where the new C# based Explorer and WinFS beta1 were way too slow. Nowadays C# based server side product development is mainly done by MS India (e.g. SharePoint). So as it seems they are back to native WinRT (COM based) and HTML5.
> Rust is nice, but [...] The only ones that stayed around have had OS vendors support
Dogfooding is important and Rust is already used in the Mozilla Servo project, a new web engine. One can imagine that a future FirefoxOS will be based on it as well (replacing the C++ based Gecko engine), as it already uses no legacy XUL GUI code. And there are Rust based experimental operating systems like from "Julia Evans": http://jvns.ca/blog/2014/03/12/the-rust-os-story/
> I suggest checking out the Wikipedia article on COM for further info.
I know COM since it was called OLE and promoted as an DDE evolution.
Developing software since 1986.
> Or maybe it will get a static compilation option similar to Go-lang that contains the GS and parts of the framework in the executable.
It already has that since Singularity, via Bartok, which was adopted into Windows Phone 8 via MDIL, which produces native binaries with dynamic linking.
Unfortunately most of the C++ vs Rust comparison posts are coming from people who aren't familiar with C++'s idioms and it's obvious that it's not their day to day / goto language.
Like all performance questions, the answer requires another question: "at what?"
There's been a number of different benchmarks, in a bunch of places. We generally end up in the same realm as C++, sometimes faster, sometimes slower.
Also, many benchmark authors seem to accidentally compile Rust code without optimizations. We're considering switching Cargo's defaults because of this.
Yeah, it might be neat. It's still unclear how good Rust is going to be at website backends, though there's a lot of people putting hard work into it. Once it all matures, seems good.
I'm considering C++ for an NLP related project. Normally I use Java/Scala, but the memory usage when you have to store a lot of strings, is pretty high. (The Stanford NLP library recommends at least 3-4Gb of Ram)
I'm considering C++ for the lower memory requirements, but obviously the syntax, etc aren't ideal. Do you have any opinion on how Rust would perform in that scenario?
I'm not mega familliar with NLP as a field, but abandoning Java means you abandon Weka, right? While Rust the language might be good for this, there's basically zero libraries, so you'll be doing all that work from scratch.
From the Rust website: "Rust is a systems programming language that runs blazingly fast". It says: blazingly fast. What more do you need? Benchmarks? Pft.
Are there examples where borrow checker stands on your way? Like the mentioned (in comments) way of building a tree. I'm sure there should be really weird edge cases.
Newcomers to Rust often find that the borrow checker _always_ stands in their way, because they haven't internalized the rules yet. I've also spoken with a few frustrated ex-C++ers, who have some sort of pet pattern that works basically all the time, but the borrow checker doesn't like the 'basically.'
In general, any kind of analysis like the borrow checker will reject some valid programs, as it pays to be extra conservative. You then slowly expand the set of accepted programs, until hopefully, it matches the true set of valid programs, without accepting invalid ones.
> I've also spoken with a few frustrated ex-C++ers, who have some sort of pet pattern that works basically all the time, but the borrow checker doesn't like the 'basically.'
It's hard to remember specifics, as this is usually based off of someone jumping into IRC and asking a question about how a certain thing works. They're often very detailed things that require intimate knowledge of implementation details. Or threads like http://www.reddit.com/r/rust/comments/31mgav/what_would_the_... , which isn't really _wrong_, but unions are hard, and so people miss them.
This kind of thing happens any time you transition languages: when I started hacking on Rust, I was trying to figure out how to do the metaprogramming shenanigans that Ruby lets me do. Ironically, Rust's metaprogramming features are my biggest weakness as a Rust programmer, I don't really write my own macros.
> In general, any kind of analysis like the borrow checker will reject some valid programs, as it pays to be extra conservative. You then slowly expand the set of accepted programs, until hopefully, it matches the true set of valid programs, without accepting invalid ones.
I'm wondering, is this possible in principle?
Context: I'm thinking in terms of sound-and-decidable type-checking conservativeness [0], but perhaps that's a bad way of thinking about this? Perhaps borrow-checking is a special-enough case of type-checking that somehow isn't afflicted by this (how?) or gives up the soundness (does it?)?
Lately I have been thinking that we are getting the abstraction wrong in programming.
In real world, abstraction is a lossy process. From "white horse" to "horse", we lose the information about color. And this lossy nature of abstraction is important, as it become simpler. This should be differentiated from saying "horse" but actually meant "white horse", because in that situation, it is not abstraction but omission. The complexity is still there (or even more).
In programming, add types or mutability increase the complexity. In that regard, Rust is simply more complex. But that is not what I commenting on. I am commenting on the trend in programing to hide that complexity by omission (such as type inference) or make it implicit such as using key word 'let' and 'var'. They only make the user less aware of the complexity at surface.
Now I am not saying language designers are not doing the best they can do (earn both honest and popular credits); but I have been thinking what might be the ideal way to handle complexity, and I only can see how we are doing it with our natural language. In our day to day business, when we say horse, we do not care about its color, and any one who interprets the sentence (with horse) is fine to imagine either a white horse or black horse or any but need understand the color is not important. So in analogy, in high level abstraction, we should be allowed to say a number with no type but be understood by the compiler that any number will do, which may even include a byte. Then the language should allow us to declare specific type as well -- just like in real life we should be able to directly say "white horse" if the color is meant to be important. Which means we should be able to specify an integer with 64 bytes long (I don't think we ever really need arbitrary precision) if we deem critical. We should note that I am not referring a dynamic type, nor an optional type system. The type is always specific at very low level (cpu level). The compiler decides for us when we don't say and we don't say when it is not important any compiler decision is ok.
Of course this is not realistic and the reason is simple. The way we do daily business leads to imprecise implementation. I say horse, and it can be implemented either as a white horse or black horse or anything in between (hopefully a common one at least). It is tolerated in our daily life because the implementation is often cheap and can be directly verified at every level. E.g. I say get me a horse, I verify your facial to see if you heard, and verify you action to see if you are about to act, and I get in between gossips to get warnings of any surprises, ..., all the way until I actually have the horse. And along any of these steps, if I find I actually meant a white horse and you are not getting a white horse, I interrupt your implementation and make corrections. In programming, writing code is expensive (programmers are paid well, takes long time), and the typical only feedback is when the code completes and the user uses the software (at which point the user is getting the feedback, not programmer), and when things go wrong, user have no idea how to make correction, and this is even true for programmers, who have no idea what is inside their libraries and the nuances of the programming languages they are using.
TL;DR We are stuck.
To solve this scenario, I think the first step is to make every one be able to program, then the implementation can become cheap, and the feedbacks can become short and quick, and the compiler can become less rigorous, and then we may be able to program like we speak -- program the way we thinks.
We could write down our problems in simple English.
We then use do a natural language parsing (NLP), and transform the output to Prolog code. We execute the Prolog code and use a search engine to find the solution. Each result is used an input for another (second) search step. We score each result and do something with it. That's more or less how IBM Watson works.
The add_one example, as simple as it is, is a horrible example.
Why not embrace value semantics and write it like:
There is simply no need for pointers or references. Write simple code first. To the heap allocation example: Even if this is a silly example, just say no to unnecessary heap allocation. Why not: Now to the lifted example: This mixes operations with lifetime management. The add_one function does not need to care about ownership. And it does not need to take ownership of its argument. Instead on the call site do: There are valid points in the blog post and I know that most snippets are meant as examples. But as someone writing C++14 you can't win me with those kinds of posts, even though I'm having a fun time playing with Rust in my spare time.For more about modern practices see: http://klmr.me/slides/modern-cpp/
Also see Howard Hinnant's unique_ptr tutorial: http://howardhinnant.github.io/unique_ptr03.html