4 ms·
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 positiv
by tekacs 12y ago
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.
[1]: http://embed.cs.utah.edu/creduce/ http://embed.cs.utah.edu/creduce/
- danieljh 12y agoI'm not missing the point of those examples. They showcase Rust vs. ridiculous C++. See my point with making the add_one function not taking a unique_ptr. This is valid criticism and valid in more complex practical examples, too.
- tekacs 12y agoBigInteger& add_one(BigInteger& num) 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.
- danieljh 12y agoYour 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. http://klmr.me/slides/modern-cpp/#9 http://klmr.me/slides/modern-cpp/#9 is exactly about this.
- tekacs 12y agoYup, 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)
- pjmlp 12y agoThat is wrong, you should be using move semantics not simple references.
- scott_s 12y agoI 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++).
- CountSessine 12y agoI 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?
- steveklabnik 12y ago> 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.
- kibwen 12y agoI 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.
- steveklabnik 12y agoAh! I misunderstood, then.
- CountSessine 12y agoI 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?
- dcsommer 12y agoAnother 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.
- e12e 12y agoSure. It would also be interesting to see how the c++ could be modernized/refactored/improved -- and contrast that with idiomatic rust.