11 ms·
More Rust compared to C++
- jarrettc 12y agoThe 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: https://gist.github.com/jarrett/fe3f24c301a586efd7c1 https://gist.github.com/jarrett/fe3f24c301a586efd7c1
- SamReidHughes 12y agoAre 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.
- masklinn 12y agoHis comment is right in that all of Rust's values do have move semantics by default (checked by the compiler), that's not specific to Box<T>.
- SamReidHughes 12y agoI said comments.
- cpeterso 12y agoThe 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? http://doc.rust-lang.org/nightly/book/ownership.html http://doc.rust-lang.org/nightly/book/ownership.html
- masklinn 12y ago> 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.
- dbaupp 12y agoI 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.
- phaylon 12y agoIt 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.
- jarrettc 12y ago> 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.
- steveklabnik 12y agoSemantically 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.
- jarrettc 12y ago> 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.
- steveklabnik 12y agoI mean in Rust. > 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)
- cwzwarich 12y agoThe 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.
- phaylon 12y agoI 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.
- rudolf0 12y agoIt'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
- aikah 12y agoyou 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.
- stormbrew 12y agoObviously 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).
- frik 12y agoOlder Visual C++ and older gcc gpp had nasty error messages.
- stormbrew 12y agoOlder? They still do. That's why I was specifically talking about clang/clang++.
- pcwalton 12y agoclang 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.
- danieljh 12y agoI'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. For more about modern practices see: http://klmr.me/slides/modern-cpp/ http://klmr.me/slides/modern-cpp/ Also see Howard Hinnant's unique_ptr tutorial: http://howardhinnant.github.io/unique_ptr03.html http://howardhinnant.github.io/unique_ptr03.html
- tekacs 12y agoYou 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. [1]: http://embed.cs.utah.edu/creduce/ http://embed.cs.utah.edu/creduce/
- Animats 12y agoThose 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.
- JoshTriplett 12y ago> Rust lacks a "nil" 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.
- Animats 12y agoRust's "Option" type is a generic enum. There is no explicit "nil".
- kachnuv_ocasek 12y agoThat's what she said.
- geofft 12y agoYou 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.
- dmitryskiba 12y agoAre 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.
- steveklabnik 12y agoNewcomers 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.' Another good example is that the borrow checker is currently based on lexical scope. See all the examples in https://github.com/rust-lang/rust/issues/6393 https://github.com/rust-lang/rust/issues/6393 . 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.
- JoshTriplett 12y ago> 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.' What kinds of pet patterns have you seen?
- steveklabnik 12y agoIt'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_rust_equivalent_of_this_c/ 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.
- toolslive 12y agoTo 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.
- jeorgun 12y agoNitpick: 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.
- alextgordon 12y agoC++ 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.
- frik 12y agoC++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.
- fiatjaf 12y agoMore Rust compared to C++: http://rosetta.alhur.es/compare/C++/Rust/# http://rosetta.alhur.es/compare/C++/Rust/#
- steveklabnik 12y agoThis Rust code is very, very, very out of date. There's a project to update things to a more modern Rust: https://github.com/Hoverbear/rust-rosetta https://github.com/Hoverbear/rust-rosetta
- fiatjaf 12y agoWhy aren't these tasks being updated at RosettaCode?
- steveklabnik 12y agoI believe they were waiting for stability so that they wouldn't introduce churn. Unsure though, I don't really participate in that project.
- halayli 12y agoUnfortunately 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.
- aliakhtar 12y agoIs there anything that compares Rust's performance vs other languages?
- steveklabnik 12y agoLike 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.
- aliakhtar 12y agoI'd be interested in seeing Rust included in https://www.techempower.com/benchmarks/ https://www.techempower.com/benchmarks/
- steveklabnik 12y agoYeah, 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.
- aliakhtar 12y agoI'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?
- steveklabnik 12y agoI'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.
- hzhou321 12y agoLately 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.