Hacker News new | past | comments | ask | show | jobs | submit login
Comparing Rust and C++ (kukuruku.co)
170 points by skazka16 on Dec 15, 2014 | hide | past | favorite | 133 comments



It might be a good idea to compare C++ and Rust and not “C with Classes and no use of compiler options” and Rust. They do have a fair point about ugly template error messages, but the remaining issues are mostly moot: Freed memory issues can be avoided using std::unique_ptr and its siblings, lost pointers to local variables shouldn’t occur when using references instead of pointers, uninitialised variables are warned against when compiling with the (hopefully standard) -Wall -Werror, implicit copy constructors can either be deleted or this particular issue can be avoided by using a unique pointer (which has no copy constructor, hence the enclosing classes’ copy constructor is also deleted), I don’t quite get the issue with memory overlap, which is mostly an issue of a function receiving the wrong number set of arguments for the way it is written. The bit about broken iterators is actually nice, but in this case could be avoided by handing an external function two const_iterator which then cannot be changed. I don’t get the problems with "A dangerous switch" and "A broken semicolons", both seem sensible constructs and if you have bugs, you have bugs. Multithreading works as expected, except that using C++11 <thread> would probably have been nicer and one would have to explicitly make e.g. the lambda passed to std::thread mutable.

All in all, yes, Rust seems to be a nice language and in particular the template system looks somewhat more understandable, but please don’t write "Comparing Rust and C++" when you’re not actually using C++ but some weird mishmash of C and C++. If there are new, delete or pointer arguments in functions in your code, you’re most likely doing it wrong.


> Freed memory issues can be avoided using std::unique_ptr and its siblings

You can use a std::unique_ptr after it is assigned to something else. That's basically a use after free.

> lost pointers to local variables shouldn’t occur when using references instead of pointers

Lost references to heap variables happen all the time in C++ when the target is deallocated before the reference is accessed. It's the same problem and Rust fixes it the same way.

> I don’t get the problems with "A dangerous switch" and "A broken semicolons", both seem sensible constructs

Really? Then you're being naïve or obtuse. Both are common sources of bugs in C/C++. Accidental failure to handle all inputs (common when additional inputs are added after the handling code is written) and subtle typos with major consequences (see Apple's "goto fail" bug).

The purpose of Rust is to eliminate causes of careless and accidental errors by forcing you to do things the right way every time (rather than permitting lazy code).


Good reply; would be better without that small sentence after "really?", though :)


> You can use a std::unique_ptr after it is assigned to something else. That's basically a use after free.

While true, you can make it throw in such scenarios.

I wouldn't be surprised if the debug builds of modern C++ compilers wouldn't do it already.


Here is some example code showing the problem:

http://pastebin.com/6wu7bcrF

I compiled it using both:

  g++     -std=c++14 -Wall -Wextra -g test.cpp -o test
  clang++ -std=c++14 -Wall -Wextra -g test.cpp -o test
GCC v4.9.1 and Clang v3.5.0.

Under neither case did it supply any warnings at compile time. In both cases it segfaults when it hits the second std::cout whilst running.


Since that's undefined behavior anything (including and especially things worse than a segmentation fault) could happen.


-Weverything on clang is what you want


That's a useful option to know, thanks. However, it still didn't detect the problem being discussed.


Still, those are just two among many.


> Really? Then you're being naïve or obtuse. Both are common sources of bugs in C/C++. Accidental failure to handle all inputs (common when additional inputs are added after the handling code is written) and subtle typos with major consequences (see Apple's "goto fail" bug).

I don’t know, it seems reasonable to me to allow switch() not to handle all possible inputs and/or fall through. Requiring for() loops to always have a body also seems unnecessary, there are cases where everything fits nicely into the three standard elements.


Rust allows you to not handle all possible inputs (but you must be explicit about it). If you look at the page again, you'll see "_ => 3", which is the rust version of a default case. This means that rust can tell if you if you forgot to handle a certain case.

As for the other things you list, I'd mark these more as "short-cuts", than features. It may take more effort to create code without them, but it gives parsing and code-readability improvements. Obviously the rust devs have made their decision here.


> You can use a std::unique_ptr after it is assigned to something else. That's basically a use after free.

No, that's a null pointer dereference.


C++ is like a fairytale forest, where staying on the one safe path through the woods (unmarked, known only by whispered lore) will give you a reliable system, but where you are constantly tempted by easier-looking diversions at the end of which lurk grues.

And heaven help you if nobody has yet whispered to you the secret of the one safe path. Or if you consulted an older textbook, and the idiom it taught has a grue.


It's pretty well known that one should read Stroustrup's book and the Meyers books. If one were to learn in a vacuum, perhaps they would take a false path, but the community is in agreement on what the best learning resources are.

As cute as your comment is trying to be, most languages cannot be learned by blindly trying things out, one should actually read a few books, try to be a part of the community and so on.


There's no such thing as an agreement. Perhaps on the learning resources, but not about which features should be used, which I think it is a better interpretation of the parent's comment.

Take the C++ that's used on Chrome and compare to a Qt application. They'll be very different, and some features will be outright forbidden, depending on the codebase (for instance, Qt doesn't use copy constructors, or exceptions, and uses its own smart pointer classes). Not even templates, if you are using QObjects.

I know of noone that uses features such as RTTI.


You not knowing anybody who uses RTTI doesn't mean much. I know people who use it. Our coding standard at work is mostly based on Meyers and Alexandrescu's guidelines.

Chrome and Qt's standards and design choices are their business and do not mean there is no recommended way to do things.


What are the reference of the books you are talking about ? Is it this two ones : http://www.amazon.fr/Effective-Modern-Specific-Ways-Improve-...

http://www.amazon.fr/C-Programming-Language-4th-ebook/dp/B00...

Note that they are both quite expensive. I am a student and it is not nothing to pay for this books.


I am talking about Effective C++, More effective C++ and Effective STL by Meyers and The C++ programming language by Stroustrup.

Effective Modern C++ is about the new C++11 and C++14 standards. I am reading it now, looks good so far.

If you can get one thing, maybe Stroustrup's book would be the best choice, but make sure you get the latest edition which includes C++11. Note that amazon.fr has the kindle versions at significant discount (there's also a bundle of Meyers' first three books) if you are ok with their DRM.


I remember being a student and not having a big budget for books. Now I have a small apartment, and no space to put the books I have.

Abe Books ( http://www.abebooks.com/ ) provides a good place to look for used copies of books. A significant portion of the books in my library have stamps on the pages indicating that they were discarded from really nice institutions.

Of course there are always free-to-read blogs, but unfortunately, good C++ blogs are harder to find than other, newer language blogs.


> C++ is like a fairytale forest, where staying on the one safe path through the woods (unmarked, known only by whispered lore) will give you a reliable system, but where you are constantly tempted by easier-looking diversions at the end of which lurk grues.

This is the best description ever. May I use it?


Of course.


>uninitialised variables are warned against when compiling with the (hopefully standard) -Wall -Werror

Not with GCC:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=18501

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55873

Obviously it should warn you, but when one of the most popular C++ compilers has been failing to report uninitialised variables for 10 years then I think that the issue is worth raising. Clearly significant sections of the C++ community don't place a high value on the accuracy of these kinds of compiler warnings.


> Clearly significant sections of the C++ community don't place a high value on the accuracy of these kinds of compiler warnings.

This comes from the inherited C culture. After all lint was developed alongside the C compiler (in 1979).

How many C developers care to use static analysis?


In case anyone reading this is not familiar with it cppcheck is invaluable for finding such things.

http://cppcheck.sourceforge.net/


> The bit about broken iterators is actually nice, but in this case could be avoided by handing an external function two const_iterator which then cannot be changed.

No, I think you missed the point, which is iterator invalidation.

http://stackoverflow.com/questions/16904454/what-is-iterator....

It's important to understand that various container methods can cause iterators to point to something else (or even nothing!).

In the example given, push_back could cause all the contents of the vector to be reallocated at a new address to make room for the new element. Any iterators pointing to the old address, including the one used in the for loop, don't get updated in this process.

More details for the interested:

http://kera.name/articles/2011/06/iterator-invalidation-rule...

In contrast, Rust only lets you have more than one reference to an object (like a vector and an iterator) if all of them are immutable. So any container methods that would cause iterator invalidation wouldn't be available inside of a for loop.

EDIT: Re-reading, I realize you might have already understood iterator invalidation, so sorry for the re-explanation, but I'll leave it up there for those that haven't heard of it yet.

I will point out that:

1. This sort of for loop is idiomatic C++ code.

2. C++ compilers don't even warn you when you push_back while having active const_iterators to your vector. Rust does, which is the point of the example. This is a case where C++'s const (logically won't change) is inferior to a stronger immutability guarantee, which Rust provides.


>1. This sort of for loop is idiomatic C++ code.

No. This is C code. In C++ you write:

    std::vector<int> a = { 1, 2, 3 };
    std::cout << std::min_element(a.cbegin(), a.cend()) << std::endl;
You shouldn't be writing a lot of loops in C++. You should be using the algorithms as much as possible.


I'm aware of algorithms and espouse them, but you're not comparing apples to apples. The example is about mutating a collection while iterating over it:

  for(std::vector<int>::const_iterator it=v.begin(); it!=v.end(); ++it) {
      if (*it < 5)
          v.push_back(5-*it);
  }
...you could do that with std::for_each and a lambda, or maybe some creative use of std::back_inserter, but you'd still have iterator invalidation problems.

The right way to do it might be to use a while loop that uses std::find_if to find the next value to insert, but I'm not sure how you'd arrive at that solution without thinking about iterator invalidation in the first place.

...and the point is that Rust effectively says "you can't iterate over a mutable collection like that" while a less battle-scarred C++ developer might not even notice the problem before deploying to production.


Actually the simplest way to dix that code ils to write

it = v.push_back(5-*it);


Don't use "std::endl" unless you intend to flush the stream (doubtful in this case). It's not a replacement or synonym for "\n".


Good points. Also, first paragraph says C++11 (too be honest, C++11 mode) yet first example has std::vector< std::vector <int> >::const_iterator. Where's auto, cbegin/cend ?

In short: none of the C++ code written in the article resembles what a well-trained, well-behaved C++ programmer uses. And the same cannot be said of the Rust code, I think. On the other hand: maybe the whole point of the article is something like 'you don't have to be super well-trained in Rust yet won't shoot yourself in the foot'.


Yes, auto will save you:

    std::vector<bool> features(const Widget& w);
    
    ...

    auto highPriority = features(w)[5];
    processWidget(w, highPriority);  //whoops, undefined behavior
This is from Scott Meyers' new Effective Modern C++, in the chapter about why you should use 'auto'. Even the new 'safe' and 'convenient' features are loaded footguns.


You really think auto and cbegin/cend make any difference here?


Compilation-wise: of course not. As an illustration of what is wrong with the code: yes. auto means you don't have to spell out the complete iterator definition. cbegin\cend is just a matter of const-correctness.


C++ programmers generally know the iterator invalidation rules though.


> If there are new, delete or pointer arguments in functions in your code, you’re most likely doing it wrong.

Candidly, Modern C++ is an idiom adopted to work around the issues of raw memory management which Rust obviates out of the box.

What the author was writing was very standard introductory C++ taught in pretty much every C++ intro book I read in the early 2000s. That doesn't make it invalid C++, it makes it, "an older idiom".

To your more general point, C++ in the hands of an expert would clearly work better than presented here. I don't think that's an issue. The bigger question is, "Can C++ in the hands of a novice be as buggy as Rust in the hands of a novice".


> What the author was writing was very standard introductory C++ taught in pretty much every C++ intro book I read in the early 2000s. That doesn't make it invalid C++, it makes it, "an older idiom".

True. But that would suggest that we also consider Rust as it was taught in the early 2000s. I don’t think this would get us very far.

> The bigger question is, "Can C++ in the hands of a novice be as buggy as Rust in the hands of a novice".

Then it would be useful to firstly point out in the article that the matter of comparison is not the whole language but merely its usefulness to novices and to secondly consider the validity of such a comparison when deciding which language to use in a new project – if most people are novices for most of the time they use the language (e.g. in introductory numerical computing course), then clearly “novice friendliness” is a sensible measure, but otherwise it’s mostly a moot issue how hard the first two months are if you end up using it for the next ten years.


These biased bashes against C++ that advertise rust get somewhere between boring and annoying recently. Yes, Rust has some interesting safety features that prevent some issues that you can run into with C++ (but not all). And yes, most of us will agree that header files suck and the rust approach here is nicer. That template error message are also PITA is also well known - but on the other hand templates are different (and more powerful) than generics.

On the other hand there are still a few constructs that I'm using using often enough in C++ that have currently no sane representation in Rust and that are astonishingly never mentioned in these articles:

- Everything that you use the friend keyword for. In Rust there is no way to access private data defined in other file (because that will automatically be another module which is also something I disagree with).

- Most things related to inheritance and interfaces. Composition is not always a better solution and Rusts traits seem still more suited for compile-time polymorphism than to runtime polymorphism. As an example try to find the equivalent of a `shared_ptr<SomeThing> thing(new SomeThing()); shared_ptr<ISomeThing> iface = thing` in Rust. And yes, I want to be able to use both afterwards.

- Enabling concurrency. Some people might disagree but I think Rust is currently mostly good at preventing multithreading issues - but not at providing good and easy ways to enable it. E.g. something like boost asio would be between hard to impossible to implement in Rust because the safety mechanisms disagree with callbacks that mutate state or Future objects (which are similar to callbacks). For some things using low-level primitives like channels (aka queues) and creating new threads might be fine, but there are also enough cases where you would prefer a simple singlethreaded eventloop.

All in all I think there are still enough areas where I think Rust still is (unfortunatly) no real alternative for C++. And as long as that's the case I think writing such biased articles isn't fair.


These biased bashes against C++ that advertise rust get somewhere between boring and annoying recently.

The main Rust community tries to discourage Rust "zealotry." For example, on /r/rust, the rules include:

4. When mentioning other languages, keep the discussion civil. No zealotry!

Anyway, as for your other question:

E.g. something like boost asio would be between hard to impossible to implement in Rust because the safety mechanisms disagree with callbacks that mutate state or Future objects (which are similar to callbacks).

I know that futures have been implemented several times in Rust, including in the standard library:

http://doc.rust-lang.org/nightly/std/sync/struct.Future.html

In general, multithreading primitives can be implemented using ordinary Rust libraries. Internally, these libraries use unsafe to do low-level threading work, but externally, they provide a safe API. The library author takes responsibility proving that their design is, in fact, thread safe.

If you know of specific boost asio features that can't actually be implemented safely in Rust, I'd love to know about them. I know that various people want to work on async I/O libraries for Rust after 1.0, and it would be nice to know about any major obstacles in advance.


Regarding the futures:

These are futures that you only can synchronously wait on (like what is currently available as std::future). However there is no possibilty to attach continuations that run when the value of the future is available. E.g. like the C++17 concurrency proposals, C#'s `Task` type or Javas `CompletableFuture`. These all rely on storing some kind of callback or closure inside the Future. That's not easy in Rust because it leads to situations where ownership is no longer obvious. The continuation might need to capture objects that are still needed locally (so you need something similar to Rc<RefMut<SomeThing>> ). In the most simply example's the continuation needs a mutable reference to the Future itself whereas the Future-producing operation also has one. And if you have possibilities that the continuation could run on another thread/executor than the calling thread (which is possible with the other languages) things get more complicated.

Regarding asio. You don't have to deep dive very much here: Simply consider boost::asio::async_write(socket, buffer, [](error_code, bytes_transferred) { /* Do something with the result */ });

You need some mutable objects (like socket or the object that contains socket) locally AND you need acccess to them in your continuation. This will only work "safely" with garbage-collected or ref-counted types and these do still not seem to work for interfaces.


This will only work "safely" with garbage-collected or ref-counted types and these do still not seem to work for interfaces.

I'm not quite sure if I understand the problem you're describing. I can definitely share mutable objects between threads, and I can do so using abstract traits (aka "interfaces"). Here's an example:

https://gist.github.com/emk/acdf3ab9c79ba1abe6d2

I had to use a "box" here, because that's the easiest way to store objects of varying size that implement a specific interface. Experienced Rust developers may be able to simplify this a bit.

Is this what you were thinking about? Or did you mean something else?


The part that your example is missing is that one side uses the trait object (Common) whereas the other side uses the "normal" type (Foo or Bar) or another trait object view of the object. And the be able to cast between such "smart-boxed" types after you acquired one of them.

See the disucssion a little bit below for examples with Rc (it's the same problem but within a thread instead of between threads).


> 4. When mentioning other languages, keep the discussion civil. No zealotry!

This article still reads like zealotry. No tradeoffs are shown, only praise. C++ examples are alien. Like GP, I had the same experience with C++ templates being able to do more things than Rust's traits (like templating by a value).


I think the emphasis was meant to be on "the main Rust community". I would hope that most of us would try our best not to misrepresent other people's hard work. I definitely agree that whilst Rust's generics are far nicer to work with thank duck-typed templates, there are still gaps when it comes to matching C++'s expressive metaprogramming. Templating over values, constexprs and variadic type parameter lists are the main ones. I do know that the former two are planned, but that will have to be after 1.0.


Exciting! It will be interesting to see if duck-typing prove more useful or not in the Rust meta world, language wars aside.


Just on your last point, Rust does have the ability to do unsafe code (and many multi-threading primitives use these internally). The real question is, how can these be exposed to Rust in a way that plays nicely with other Rust code.

There are things like RefCell, which enables multiple threads to hold a reference to an object, with run-time checking of borrows. Or there are also explicit mutex wrappers that can be used. You could use these with channels, closures, or a callback system.

As Rust matures, we should see more of these kinds of features find themselves into the standard library.


The real question is, how can these be exposed to Rust in a way that plays nicely with other Rust code.

Yes, that's exactly the question I'm asking myself. I know that you can do everything in unsafe, but as soon as I step into that mode I'm getting the feeling that it might end up in the same level as C++ or probably even more complicated.

It probably shouldn't have to do with dealing explicitly with mutexes. If you have to I think that things like std::sync::Mutex are no good solutions because they are not really general purpose. If you really deal with low-level multithreading problems you often have multiple mutexes that might need to be locked in a special order and the mutexes are normally part of a data structure and do not own the data.

But multithreading is only a part of the whole problem. The other one is achieving easy-to-use and safe concurrency in a single thread (like Javascript APIs).


There are things like RefCell, which enables multiple threads to hold a reference to an object, with run-time checking of borrows.

RefCells do not allow multiple threads to hold a reference to an object because they are not Sync. They only permit dynamic enforcement of Rust's borrowing rules for references, through "interior mutability", as you said.


shared_ptr<SomeThing> thing(new SomeThing()); shared_ptr<ISomeThing> iface = thing

Rust is not OOP language, so it's logical that it can't do this. Rust got type system more like Haskell, and for me it's a big plus as I don't like C++ OOP that was taken from Simula and not even from SmallTalk. I don't like talking like this, but C++ OOP is complete trash(beat me for talking like this).

Also you are saying that Rust is not alternative for C++ in some ways trying to find what Rust don't have that C++ have. Those languages are completely different conceptually, but they target the same level. Rust is good alternative for C++ even now, taking for consideration that it's not stable(1.0 version will be soon, but it's not there yet).

There is no areas where Rust is no alternative for C++, because you can tackle any low-level problem with it, that you can tackle with C++. I don't want to say what language better, because it's not the best comparison. For now, all I can say that Rust is more sense full and more conceptually accurate, it's syntax is more concise and easier to understand than syntax of monstrous language like C++.

For the first in the long time we can say that we got low-level language with ability to write code using high-level abstractions and it will be effective. We don't need to drop code understanding for low level optimizations, yet we have power to fully optimize bottlenecks.

It's not silver bullet for sure, but guys did a big job here, showing that low level language can be more clear for human.


> shared_ptr<SomeThing> thing(new SomeThing()); shared_ptr<ISomeThing> iface = thing

Not sure what you're trying to do there, but Rust can certainly alias one object with more than one trait reference. In this particular case, you're using two mutable references to the same object, which Rust doesn't allow. But if both were immutable references, there wouldn't be an issue, assuming the right declarations preceded your code.


It was my example, so I try to explain it a little more:

There are quite a lot of cases where you would like to have things like this, but the example that's probably most straightfoward to understand is GUI systems:

E.g. you have a generic `Widget`, the `IWidget`. Then you have some base-class `Container` which contains multiple other widgets (`list<IWidget>`). This might contain a reference to a `TextBox` (which is an implementation of `IWidget`) and another widget (or controller or whatever) might contain also a reference to the same thing. Both might need to be mutable. And in exact that moment you have code like the described one.

That's no borderline usecase, it's a thing that you in all popular HMI frameworks (and in C, C++, C#, Java, ...). Maybe you can solve the issues in a completly different fashion, with resorting to only immutable objects (like some approaches for ClojureScript). But these are then no longer zero-cost abstractions versus current technology and they are not that easy to pick up for most mainstream programmers (which expect C++ alternative and not a Haskell alternative).

Besides that people are already asking for bindings to things like QT - and that will require similar constructs.


I'm not aware of any production quality UI toolkits in Rust, but using traits and reference counting, Rust certainly supports this use case.

In fact, it supports it more flexibly since different widgets don't have to share the same methods and data at all... well, unless you want them to. And you can have your widgets implement more than one trait without having to combine them all into one giant class definition.


As soon as it supports storing trait objects in smart pointers and casting them. That's afaik still not the case.


You can as long as they are immutable objects. Again, you can't alias mutable references, even if they are of the exact same type.

Maybe you should read this: http://doc.rust-lang.org/guide.html#traits


I believe your parent is referring to storing trait objects in something like Arc<T>, which is not supported: currently, Arc<T> requires T to be sized.


Similar, but not exactly:

As I said, the objective is to have access to one object (in it's native form or in an interface (supertype) form from different locations. For that you can can use Rc. To be able to mutate it you can add RefMut and end up with Rc<RefMut<T>>, e.g. Rc<RefMut<IWidget>>. And now how do I get an Rc<RefMut<TextBox>> or any other type like &mut TextBox back from that?


I think you could store objects of type Box<DerefMut<IWidget>> and then write a generic wrapper struct which holds an Rc<RefCell<T>> and derefs into the trait object.

Admittedly this is ugly, and unnecessarily slow. For a more general solution, I think the language would allow implementing some kind of RcTransformed type which, given 'T, U : Unsized?, Transform : Fn(&T) -> &U' (or possibly a more general type, but I think that would require HKT?), takes an Rc<T>, calls the transform, and stores both the reference and the transformed reference, allowing direct access to the latter. This would require an unsafe implementation, but would be safe to use.

Alternatively, there may be some existing functionality like that that I just haven't heard of...


Three questions:

1. what is the technical difference between template and generics? 2. how do we know that what Rust has are generics? 3. considering that the Rust type system was recently demonstrated to be Turing-complete, how can it be less powerful than C++'s?


I'm not the person who can give an exact definition, but imho generics are more related to the type system (you can have types that refer to other types) whereas C++ templates can produce more or less arbitrary code. E.g. the field of template metaprogramming in C++ is not possible with generics.

I'm personally not convinced that the extra possiblities and extra complexity of C++ templates vs. a good generics implementation in other languages is worth it, but I just wanted to highlight that the comparison is not fair. And hopefully we will get better error messages with C++ concepts, which will probably compare better to Rusts generics and kinds.


Tangential, but I'd like to point out that you can achieve compile-time metaprogramming much more easily in D, which provides the entire language during compilation [1]. I find this to be much more straightforward than arcane uses of templates such as SFINAE [2].

[1] http://en.wikipedia.org/wiki/Compile_time_function_execution... [2] http://en.m.wikipedia.org/wiki/Substitution_failure_is_not_a...


I've flagged this because not only is it an unfair characterization of C++, it's also using a version of Rust that is six months out of date.

As a Rust fan, I don't think we need to resort to cheap shots. The language stands well enough on its own; misinformation and propaganda pieces won't get us anywhere.


If anything, the Rust community seems more concerned with only showing the good sides of its community than it is with only showing the good sides of Rust. That of course has its social/"political" advantages.


I see this as a technical advantage, not a political one. I want the language to be legitimately great, which means that we need to encourage an atmosphere where people feel welcome to express informed and constructive criticism. Breathless praise is surely kind, but it doesn't really advance the conversation. In other words: I already know that the language is good; I want you to tell me how it could be better.


Raw pointers is a bad example to compare to Rust. It's more like comparing C to it. If anything one should compare managed pointers like std::shared_ptr and etc, especially since the author mentions C++11.


One thing important to keep in mind that unique and shared pointers are actually _much_ less common than borrowed pointers in Rust. I have little C++ experience, but I can borrowed pointers in Rust are used exactly like the unsafe pointers that would be used in the same situation in C. In other words borrowed pointers allow you to write the same code you would in C, but safer. [Other Rust features necessitate or strongly encourage different coding styles.] This leads me to conclude that C++ smart pointers either give you less ergonomics, or less safety, than Rust.


What do you mean less common? Less commonly used or less commonly useful? Idiomatic C++11 discourages usage of raw pointers altogether. C++ introduced RAII ideas quite a long time ago, it's just in practice they weren't always followed and only in C++11 they were backed by standardized features from stdc++. Rust has an advantage of following this approach from the very beginning and not having all kind of legacy baggage.


To be clear: Rust has quite a few smart pointer types, but because Rust makes regular references completely safe, people just use those a lot of the time. For example, very few functions are written to take `Vec<T>`s (the equivalent of C++'s `std::vector`), because they can simply take slices (`&[T]`) which are just a pointer and a length. Storing a slice in a structure isn't unsafe in Rust, so people do it all the time. The same goes for strings (Rust's `&str` is equivalent to C++'s `std::string_view`). It is also very common to pass references directly into slices, strings, or other data structures rather than copying, allocating, or moving, since it's completely safe to do so. You end up only really requiring explicit smart pointers for recursive data structures, or in very rare cases where you need complex ownership behavior.


Thanks for clarifying. That's good, since I assume using smart pointers is more expensive in Rust than using references, same as in C++.


Reference counted smart pointers are more expensive than regular references in rust, but not as expensive as those in C++: Regular Rc pointers live on a single thread, and so don't have to use atomic increment/decrement like they do in C++. In addition, Rust's support for borrowing means that when passing smart pointers around you typically don't need to increment/decrement the pointer at all - because the compiler statically enforces that the borrowed reference lives for a shorter time than the smart pointer it's borrowed from.


Not only legacy, many hardware vendors provide only C bindings leaving the management of resources to the user.


How many hardware vendors provide any kind of Rust bindings at all? Let's compare like with like.


Maybe a more fair example would be std::fstream from C++, which takes a `char const ` (no size integer) or a `std::string` (a.k.a. string buffer) as constructor arguments. In the most idiomatic C++ possible, the standard library would support two character iterators.

In Rust, the equivalent might be:

  let ioResult = File::open(&Path::new("foo.txt"));
...no unsafe pointers or mandatory allocation necessary (1). Plus you're forced to check your error condition by unwrapping the returned value.

I don't bring that up to pick a nit, but to point out that this is basically the state-of-the-art (well, the char might be paired with a size_t) for passing strings through C++ APIs.

(1) I'm not sure whether Path::new actually allocates, but there's nothing stopping you from implementing a tight-and-fast InPlacePath that works seamlessly.


Both, because you don't need them as often.


Not sure, I used them quite extensively in various projects and tried avoiding raw pointers. The only concern can be some cost of overhead when using them.


Also failed to use std::thread and its related synchronization primitives.

And nobody who's been writing C or C++ for more than five minutes would actually make that mistake with a switch statement (omitting breaks). On the contrary, its fall-through behavior is often very convenient.


I missed a break in a switch a few weeks ago. I have been programming in C++ for more than a decade.


There have been actual real world examples of simple things like one line if statements and forgetting switches breaking code in production software and leading to VERY dangerous things. Especially in C/C++ code.


I have to agree with you. It's never registered as a problem for me. I think of it like an ordinary kitchen knife. Yeah there's an off chance I might cut myself, but 99% of the time I'm just going to enjoy a steak.


Comparing languages with constraints which were laid in bedrock 30 years apart is fairly uninteresting. C++ is constrained by its compatibility with C, and always will be. Even C++17, which will be the biggest shift in the language ever, won't change this.

I see very little evidence that most programmers actually care about the advantages Rust the language, as specified today, brings. I'd contest that these days the biggest reason competing languages like Go, Rust, Python, PHP and Java often trump C++ is less to do with the language, and more to do with these languages having sizeable and fairly decent standard libraries. Programmers want to get shit done.

The C++ standard library is tiny and has seen fairly paltry growth over the years, with only modest introductions in C++11 of things like regex and threads. If a set of filesystem, HTTP, networking, and serialization libraries were part of the standard, and well implemented, we may well see a different landscape.


> more to do with these languages having sizeable and fairly decent standard libraries.

Rust actually has a pretty small standard library. Since we have a package management system so early in the lifetime of the language, we're leaning on that rather than having a large standard library.

Just two days ago, a PR was opened to remove four more things from the standard library: https://github.com/rust-lang/rust/pull/19820


HTTP is welcome, but I'll take logging, JSON, XML, unit testing, and a database interaction layer before that. Most of those are types of "serialization", I guess, but I'll go ahead and be a little more specific.

And a type-safe dependency injection framework wouldn't be the worst thing in the world either.


HTTP is a continued pain point for C++ I find. It falls in to an impossible valley of "too simple for anyone to have bothered writing a really good standalone library" and "too complex to write myself". I don't see anyone jumping at the chance to build a HTTP request state machine built on top of nothing but the standard library when the standard library has no standard solution for efficient asynchronous operations or task scheduling.


I'm not saying it's not needed, but there's a huge chunk of the C++ world that doesn't do web development. The other things I mentioned are more generally useful. In particular, XML support probably needs to come before HTTP support.

Not that I'd turn my nose up at standard HTTP support.


I recommend the libraries from CodeSynthesis for your XML and database needs. The documentation for both is superb, and the APIs are crisp and modern. JSON I'd go with either JSON Spirit (boost header dependency) or JsonCpp (git version[0], since there hasn't been a release in 3 and a half years. 1.0 was tagged recently though)

For webdev I've personally used CppCMS, which is of good quality, although it is a framework and not a toolkit (it's MVC and the views want to use inheritance, has its own networking and template framework etc)

[0] https://github.com/open-source-parsers/jsoncpp


I know there are libraries out there; I was talking about getting more things standardized so programs can mix and match these libraries with fewer translation layers. For example, boost spirit is a bit out there for most projects.


It's tricky and there is no one-language-to-rule-them-all because they have up- and downsides.

C++ has the advantage of an incredible amount of code written in over the years that mostly works. It also executes fast. It's also easy to find developers for it. With the new addons to the specification it's even possible to write readable code with it.. However, it has a stupidly long, verbose and feature creepy standard (well over 1000 pages), which results in no developer reading it. Because of it's design, compilation times tend to take long (as a ridiculous amount of headers are included even in the most simple applications).

Go is pretty much the opposite. It has a lean and readable spec (on purpose) and a fairly common canonical syntax without much discussion about it. Because it's relatively new, there is not much written in it however. It has some specific strengths, like a concurrency implementation that abstracts most of the process/thread/mutex stuff. With this, it does appeal to new projects that don't depend on libraries written in other languages. There is no point in porting multi-million SLOC applications to Go though, as that would be incredibly time-consuming. It has the potential to surpass C++ in a few decades though, as it operates in the same domain (systems programming).

Python is a versatile scripting language. It has nice syntax and is incredibly flexible at manipulating data. Add to it the well documented, feature-rich standard library. With it you can evaluate data with incredible flexibility and it has a fairly complete feature set for general programming tasks that span multiple OSs. The downside is that it's slow in execution and needs the interpreter along the scripts. Especially for smaller tasks and simple automation it is the first choice however because of it's small footprint in constraints (you can just edit and drop a scriptfile anywhere on a Linux system and it works).

Java.. I'm not entirely sure what Java does well. I hear praise for the JVM, but generally I get the impression that it is plagued by legal issues, alien looking GUI tools, more than verbose syntax and it also has a stupidly long specification (almost half of C++'s). Maybe someone can shed light on this one. Tools maybe?


The C++ language specification is only ~450 pages, the rest of the ~1200 or so pages is the standard library specification.

> Java.. I'm not entirely sure what Java does well.

* Package/module system is dead "drop your JAR here" simple.

* Humongous standard library.

* Oracle provide a really powerful free JIT (probably still one of best for real world performance when it comes to JITing static languages)

* Standardised language specification (like C and C++)

* A well specified, really simple bytecode that other compilers and runtimes can target.

* It's a newbie friendly, procedural, object-oriented language that encourages 'patterns' that make developers feel comfortable and that they know how to tackle problems (whether it's the right way is another matter)

* Relatively safe, meaning there is less to learn (no pointers, memory management, arithmetic overflow etc).


    > [Go] It has the potential to surpass C++ in a few decades though, as it operates in the same domain (systems programming).
Nope, Go and C++ operate on different domains, Go is garbage collected and nobody will use it for low level system programming.


I'd disagree that Go is a systems programming language in the way that C++ is a systems programming language. As in, I'm not sure you would implement a garbage collector in Go. However, both Go and the typical processor are evolving, to that may change over time.

To be fair, if I were wanting to find all matches for a regular expression in a given text file, I sure wouldn't use C++, even though C++ is certainly capable of doing that.


While I love what Rust is trying to achieve and hate the many, many issues C++ has, this reads like a blatant propaganda piece.

It's not comparing C++ with Rust, or at least not fairly. It's just listing the problems of C++ that Rust is trying to solve. Someone well versed in C++ could easily write an article with the same title and list a dozen reasons why C++ is superior.


It's not meant to be fair. Rust is created as a successor of C++ to completely overtake it everywhere.


Yes, but everyone knows that it won't do that, and anyone who doesn't have brain freeze from the Rust Kool Aid understands that in programming languages, it's never so clear cut.

I realize that Rust was designed to address the issues with existing systems programming languages, but surely it has flaws, tradeoffs and small annoyances, like every other programming language.

I really like Rust and see the value in what they are trying to achieve, but I'm realistic enough that it won't be perfect.


> surely it has flaws, tradeoffs and small annoyances, like every other programming language.

Rust core team member here: it absolutely does. Rust is _far_ from perfect, as much as I love it. Anyone who tells you Rust has no flaws is lying.


Can you describe in few words which flaws do you see in rust?


Of course it does, but then it wouldn't be a comparison of C++ to Rust as much as a list of things Rust doesn't do well yet.


If I only get a penny every time I hear this "C++ is dead, the <insert_new_language_name> is going to replace it everywhere"..


The proposition is so far-fetched that I suspected that he was being facetious.


Normally I would think so too, but after so many overzealous articles about Rust on HN lately, it's hard to say.


One of the main problems of C++ is that programmers treat it as C with classes because that's what it feels like at the surface. They think they know it because it feels like C and many decide not to learn its patterns and details. From the examples given, it's clear that the author is not very familiar with C++ and is also comparing Rust to C++98 which is not fair. C++11 is almost a new language.


How does Rust handle multiple object files and dynamic linking? It seems to me that pretty much all of these guarantees break down if the compiler can't see the source code for the whole program at once.


All the information needed to make these guarantees is the type information of all visible items in a translation unit ("crate").


I'm skeptical. Did they really make the type information stored in the ABI that advanced? Storing that kind of information outside of name mangling is a nontrivial feat (i.e. they'd need to rewrite the linker) and there's not a whole lot of information you can pack into name mangling.


I believe it's stored as the value of a special symbol in the object. I'm pretty sure I read about it somewhere, but I can't find the link now.

Does anyone know where to read about how Rust handles this?


It basically generates a header file from the source code and then stores it alongside the object data containing the type information of all public items.


So there's no run-time dynamic linking? Or is it possible, but only with additional metadata files present? Or maybe the assumptions are statically verified according to the interface and it is assumed that they are never broken by clients?


The last one. Types are stored as metadata in the object files, those types are verified at build time, and then assumed correct at run time.


Gotcha, thanks!


They aren't stored in the mangled names. Types and other metadata on items are encoded into a special metadata file that lives in the archive.


Like C++ interfaces that contain std::unique_ptr, you can encode Rust types in your mangled names during linking.


I'd actually much rather read the opposite article: things that C++ excels at that Rust falls down on. I'm not an expert, but I think there's still allocation use cases C++ is better at.


Off the top of my head:

* C++ has more robust compile-time meta-programming facilities, at least for the time being.

* C++ can generally include C (well, C89) code as-is with no translation layer. Rust's FFI is pretty inoffensive, but it's more work than a #include

* It's easier to do the really dangerous stuff if you have a good reason to. You can actually call push_back on a std::vector without invalidating your iterators if you're careful. I've never seen it used properly in production code, but it's possible. At any rate, it might be more awkward to write equivalent code in Rust. Then again, maybe a deque is better than a vector for this use case, in which case I suspect that Rust is capable of a more optimal solution than push_back or std::back_inserter.


C++ can also easily bind to libraries written in C++, while Rust cannot.

(those libraries need to expose a C interface for Rust to be able to)


That's true, though I blame C++ for its lack of a standard ABI more than I would blame Rust. Not that that helps if you're just trying to get work done.


Rust looks really promising. Unfortunately, the tooling just isn't there yet, it seems. Release a working plug-in for YouCompleteMe and I will switch overnight ☺


It's true that Rust's tooling is lacking, but if you're just looking for autocomplete for vim, https://github.com/phildawes/racer#vim-integration exists. I haven't tried it myself, but I hear good things.


Does UB enabling code optimization? The 'dangerous switch' might run faster if the compile deduces that only two of the possibilities are valid?


If I understood correctly, it is not possible to manually manage memory in Rust and do things like memory recycling or pools?


No, you can do that, and can do so safely (if you implemented the unsafe parts correctly, at least). For example, here's an arena allocator crate: http://doc.rust-lang.org/arena/


Are those annotations like Java's?

http://doc.rust-lang.org/src/arena/lib.rs.html#353-363

I wish languages would let you say the whole file/module is special - for example, inline all functions.


Not sure about inlining, but if you put #![some_attribute(args)] at the top of a .rs file, it applies to the whole file.


>No, you can do that, and can do so safely (if you implemented the unsafe parts correctly, at least).

That is equally true of C++; you're completely safe as long as you don't make any mistakes.


The entire C++ language is an unsafe block. I think you're trying to be funny, but you missed the point.

A concrete example of safety is that Rust can tell you if you reference memory from an arena after the arena goes out of scope. C++ can not.


You're not wrong. Rust's contribution is that it statically verifies safety as much as possible, and when not possible, at least isolates all code which could potentially be unsafe. The means that if the program segfaults or has a data race or other unsafe behaviour, the programmer need only focus on code marked as unsafe to fix it.

In practice, most applications have hardly any unsafe code, delegating all of their unsafe behaviour to libraries that have presumably been thoroughly tested.


If by true you mean false then yes I agree.

There is a world of difference between this block/function is unsafe rest is enforced by compiler and everything written is unsafe, lets download third party program to check.


There's a lot of unsafe code in there.

The only allocation that really needs "unsafe" is "Vec", which is needed to have some way to convert raw storage into variable size arrays. Everything else could be built on top of "Vec".

If you want the effect of an arena, you can have all the objects in the arena owned by a master object, and all the links within the arena weak, using "std::rc::Weak". This is a reasonable way to do a GUI or a web page, where there's heavy interlinking to neighboring graphical items, but those links don't indicate ownership. When the window or page closes, just drop the master object and the arena goes away. Yes, you spend more effort on deallocation. That's not where the time goes in a GUI.

There's an argument for doing things that way, rather than sprinkling "unsafe" around the library. Make it solidly reliable first, then profile, then look into compiler optimizations to make it faster.

(There's a macho mindset with some programmers. They think they don't need all that checking. They're wrong. Put those guys (it's usually males who have that attitude) on fixing obscure bugs or doing crash dump analysis for a while. They'll learn.)


Ugh. This is what is wrong with any discussion with performance; someone who only ever builds CRUD apps wades into the conversation spouting junior level truisms and raises the noise floor for those of us who have done steps 1 thru 3. This isn't Stack Overflow, you aren't educating beginners, and even then there is no need to come to the comments with an axe to grind.

The compiler is not a magical fairy that optimises away the cost of memory allocation. You don't 'emulate an arena' with a vector of reference counted pointers, any more than you emulate a cheetah by painting spots on your body.

Nobody uses an arena because they are lazy or hate destroying objects; you still pay the cost if your objects have the Drop trait. You use them because you have a bunch of fixed size allocations all with the same lifetime, and - after profiling - you need to exploit that fact.

There will be unsafe code outside of Vec because you need to do four things:

- allocate a chunk of memory (for non-relocatable objects) - construct objects in portions of the memory of that chunk - destruct objects in each chunk - deallocate the chunks of memory

If Rust ever gets placement box we can do the whole thing safely, but IIRC there hasn't been a decision made nor any experimental implementation. Until then, if I need to turn off the safeties to get something done, I will - not to spite the condescending 'enlightened' who are above getting their hands dirty, but because if there is a business need and it can be done correctly with due diligence, it is not 'macho', it is the professional thing to do.


You make good points but the ad hominem is really not necessary.


In the cold light of day I agree, but we're not robots; discussions have an emotional aspect to them and sometimes an emotional response needs to be expressed. You can't be that person who tells everyone "you're doing it wrong" unless you really know for sure they are, and even then you shouldn't make them feel bad about it.

It's worse when the subject is a nascent project and people's ideas are forming out of discussions like this, but in general it has a chilling effect on the subject being discussed. In this particular example, we need to know the deficiencies of Rust's allocation model, and there is much to be gained by discussing them and finding a workable solution. Shutting down the discussion with 'use the safety tools you already have or you're a foolish brogrammer' is not acceptable.


Rust is a new language. Its designers are close to solving the problems they set out to solve, but they haven't fully succeeded yet. Heavy use of "unsafe" indicates that there are things you can't properly express in the language, or can only express inefficiently.

It's worthwhile to separate the two. If you can't do it at all, there's a problem with expressiveness. If you can't do it fast, there's a problem with performance. It's useful to try to do things without using "unsafe" to cheat the system. Then you find out what the language needs to do faster. That's a trouble spot to be identified. There may be an optimization which leads to a safe and fast solution. Or there may be a way to make something checkable so that it's not necessary to do something unsafe.


There are certain classes of behavior that are safe but compiler flags them as problematic.

To get around this restriction you use unsafe block or function.


The one thing I don't like about Rust is the implicit return. Looking at code, it's easier to spot a missing/extra "return" than a missing/extra ";".


If you put an "extra" (unintended) semicolon on the last expression, the function will return Void/Unit: if this doesn't agree with the given type of the function, then there will be a compiler error. If you forget a semicolon, then the type of the function will become the type of the last expression, which will again make the compile complain.

Are there other issues that you had in mind?


The C++ will complain as well, the issue is ";" is 6 times less characters than "return" and so, easier to overlook.


I think I'd rather have that problem than this problem:

    if (cond); //oops, a semicolon
        alwaysHappens();
, thank you very much.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: