
Short intro to C++ for Rust developers: Ownership and Borrowing - ingve
http://nercury.github.io/c++/intro/2017/01/22/cpp-for-rust-devs.html
======
banachtarski
Oh boy there are some serious errors here.

Returning a stack allocated value does not invoke the move constructor. It
doesn't copy either. This is a separate optimization that existed even prior
to C++11 called the "named return value optimization" or NRVO for short. And
by "sufficient new compiler" you really should just say every compiler. No
compiler I know of in existence used by actual people doesn't implement this.

Here's a demo: [http://ideone.com/QRqj5P](http://ideone.com/QRqj5P)

It's VERY important that this optimization isn't confused with move
construction as the latter would actually be extremely less performant than
what actually happens.

~~~
krona
Yes, but rust on LLVM will also perform NRVO, will it not?

NRVO is a compiler optimization, not a feature of the language itself.

~~~
dbaupp
Rust doesn't have user-defined copy constructors and a return is always a move
(semantically a memcpy), so I'm not sure it's so relevant as an optimisation
for Rust.

~~~
echelon
You can't manually implement the `Copy` trait?

~~~
masklinn
You can but it has no user-defined methods related to it, it's just a marker
trait. Move and copy are both straight memcpy, the difference is whether the
source remains valid afterwards, not what operation is performed.

------
sbuttgereit
I don't come from a even vaguely C or C++ background, but have been studying
Rust for some time; most development work that I do is executed in very high
level languages. I know generally the concepts that exist in C & C++ and
absolutely no idea how and why you'd use them as opposed to other tools (OK...
an overstatement, but you get the idea). Much of this is because I've not
tried to write any C/C++ and only even read it when I need to generally know
what some feature of an application does.

This article actually helped me understand C/C++ a little bit better than I
did before. For example where it explained the differences between Rust and
C/C++ copy vs move behavior... I got a better sense of just how C/C++ works
and why I might chose to write, say, a function parameter one way vs. another.

So in this basic sense, mission accomplished I think. I have no doubt there's
lots of nuance missing, but still... not bad and I appreciate the effort.

------
ThePhysicist
The important thing to remember about `std::move` in C++ is that it doesn't
move anything, it simply does an unconditional cast to an rvalue reference,
which basically allows you to treat the object as a temporary variable
(effectively transferring ownership).

------
be21

      In most cases maintainability wins,
      and avoiding “premature optimization”
      is very much a necessity in C++.
    

I agree with this conclusion. Quite often, when you start coding you don't
know, where the performance bottlenecks hide, and you don't want to waste
time, thinking about allocating memory for a routine which you could write
equally well in any scripting language. Unfortunately plugable automatic
garbage collection for less verbose performance uncritical scripting tasks
within the language is not usable.

------
solidsnack9000
This really puts the ownership features of Rust in perspective: they are
certainly no more involved than what's going on in C++. Harder than Python,
yes; excessive compared to Haskell, sure; but here we see what Rust is really
comparing itself too. (This is not to say, that it's always what we should be
comparing it to.)

------
heinrich5991
This glosses over "rvalue references", which is roughly what Rust does when
you pass a parameter without any further qualification (references, boxes).

~~~
ycmbntrthrwaway
Can you give an example?

Is

    
    
      void foo(T&& value) {
        ...
      }
    

similar to

    
    
      fn foo(value: T) {
        ...
      }
    
    ?

~~~
krona
Both are similar in the sense that `foo` _owns_ `value`, yes.

Or at least that's the idea. In C++, it's up to the author of T to implement
such semantics (through its move constructor) correctly for non-trivial types.

~~~
Noughmad
I wouldn't call this owning, a better description is that that foo is a sink
for the value.

~~~
msbarnett
Rust _would_ call that owning, though.

------
general_ai
Therein lies the perception problem: a lot of folks think that C++ is a static
target, but it's not. It has improved by leaps and bounds over the past decade
or so, and will improve a lot more this year when C++17 support becomes
widespread. I find anything from C++0x onwards pretty pleasant to work in if
you're on a UNIX-like OS. Windows is another story entirely.

~~~
pjmlp
> Windows is another story entirely.

Actually Windows has always enjoyed better C++ related tooling than any UNIX
other than Mac OS X.

And standards compliance is actually the best one among commercial C++
vendors.

~~~
general_ai
That's what Microsoft wants you to believe. In reality C++ tooling is vastly
better on any Unix, and it's 100% free in both senses of the word.

Also, there's no qualitative difference between C++ toolchains on macOS and
Linux, since the same compilers are used on both.

~~~
pjmlp
Yet another UNIX user that never used Borland C++, C++ Builder, Zortech C++
debuggers, Visual C++...

> no qualitative difference between C++ toolchains on macOS and Linux,

Where is Instruments, XCode, Cocoa, IO Kit for GNU/Linux?

~~~
oselhn
I use Visual C++ 2013 at work and it is quite bad. It is just smarter text
editor. There are only most basic refactoring features, even find all
references is not reliable (it is text based search). You have to buy another
extension to make it usable (Visual Assist or Resharper). Project files are a
mess. There is vcxproj file to define build and vcxproj.filters file for
directory structure view in VS (I do not understand a reason for this).
vcxproj.filters is often broken by VS. It sometimes inserts duplicated lines
for no reason if you add or remove some other file from project. Debugger is
quite good but still I have some issues(you can't set hexadecimal format just
for one variable, it is global option). Overall I have better experience with
Eclipse CDT (more features) or Code::Blocks (almost same features but faster)
than with VS without extensions. Resharper makes it good IDE but it cost
additional money and also it is not fastest extension.

There are reasons for bad code navigation and missing refactoring features:
[https://blogs.msdn.microsoft.com/vcblog/2015/09/25/rejuvenat...](https://blogs.msdn.microsoft.com/vcblog/2015/09/25/rejuvenating-
the-microsoft-cc-compiler/)

~~~
stinos
_There are only most basic refactoring features_

Ha I always thought there were actually none.

 _even find all references is not reliable_

Yup that sucks. It's better in VS2015

 _Project files are a mess._

Yeah it's xml but for the rest it just lists files and options, how is that
much of a problem?

 _There is vcxproj file to define build and vcxproj.filters file for directory
structure view in VS (I do not understand a reason for this)._

Can be easily solved by ditching the filters file all together and enabling
'All Files' option in Solution Explorer. Unless you insist on having all files
listed by extension, which is imo a useless complete mess for enything but
small projects.

 _you can 't set hexadecimal format just for one variable_

You can, use '<variablename>, h' in the watch window

~~~
oselhn
_Can be easily solved by ditching the filters file all together and enabling
'All Files' option in Solution Explorer. Unless you insist on having all files
listed by extension, which is imo a useless complete mess for enything but
small projects._

Does not work for me, because files are located in subdirectories. I see only
long list of files from all directories without filters. Maybe it is just bad
project structure but I cannot change it. I found plugin for 2015 which is
able to generate filters from directory structure, but I have to wait for
upgrade.

~~~
stinos
_I see only long list of files from all directories without filters_

Not if you select 'Show All Files' in solution explorer, then it shows the
entire subdirectory tree.

------
andy_ppp
This is probably the best explanation of Borrowing and Ownership I have seen.

------
rosshemsley
One useful rule of thumb that works 99% of the time in C++11 is:

Never use "std::move"

It's really just there to allow fancy optimisations and you don't need it in
most cases. You should rely on the sane defaults instead:

\- Putting variables on the stack to manage object ownership/lifetime is a
good default.

\- Use std::unique_ptr or std::shared_ptr() for heap ownership (single
ownership vs. shared)

\- For a non-owning (mutable) reference, use const & (&) or const * (*) when
the value may not exist.

\- Always prefer passing by value (return types and parameters)

\- If a parameter type is heavy, take a (const) reference.

\- If the return type is heavy, the compiler's RVO will do its job.

No need to be more fancy than this

~~~
konstmonst
I disagree with you opinion on that. If you don't understand something doesn't
mean that this is just for fancy optimizations.

Essentially you use std::move when you want to have pointer semantics without
using a pointer. You don't want to use a pointers sometimes to keep objects on
stack and use it to track ownership.

So instead of doing C* c1 = new C; C* c2 = c1; You have C c1; C c2 = c1; //
here I have a move constructor which moves c1 to c2, c2 is now a null object.

This is what rust is able to do and you don't need expensive atomics
(shared_ptr), ugly template syntax (unique_ptr), can keep everything on the
stack and (!) have the objects be automatically cleaned up correctly.

~~~
rosshemsley
> If you don't understand something doesn't mean that this is just for fancy
> optimizations.

You should definitely understand it, but if you find yourself wanting to use
it, you should start by considering alternatives first. It's a sharp (and
often unsafe) tool that should be avoided in most cases.

For your example, unique_ptr is the correct (and safe) solution and will do
the same thing.

> you don't need expensive atomics (shared_ptr)

Note that shared_ptr provides no "atomicity". It is in reality exceptionally
cheap.

------
timClicks
A very good resource for C++ developers is Nick Cameron's r4cppp tutorials
[https://github.com/nrc/r4cppp](https://github.com/nrc/r4cppp)

------
criddell
I have a C++ question. In the article, the author writes the Person
constructor this way:

    
    
      Person(std::string first_name, std::string last_name)
        : first_name(std::move(first_name))
        , last_name(std::move(last_name))
      {}
    

For string parameters, do you need to use std::move()? Won't the compiler do
that anyway?

And does anybody really put the comma separating initializers at the start of
the line? Yuck.

~~~
kchoudhu
I do. Easier to comment things out when necessary.

Hangover from SQL as well.

~~~
GrayShade
It's the same. You can comment out the last line easier, but not the first
one. With trailing commas, it's the other way around.

~~~
masklinn
> With trailing commas, it's the other way around.

If the language supports trailing commas[0] (rather than just interspersed
ones) that's not an issue as you could write:

    
    
        Person(std::string first_name, std::string last_name):
            first_name(std::move(first_name)),
            last_name(std::move(last_name)),
        {}
    

[0] as Python, Ruby, Javascript or Rust do

------
jeremyjh
If you don't want unnecessary copies, the first tool you should reach for in
C++ would be a const ref. I can't think of a reason I'd want to copy a string
implicitly - better to pass a const ref and explicitly copy it so that the
next guy (me a week later) doesn't waste time figuring out if that was a
mistake.

~~~
StephanTLavavej
That guidance isn't correct in the C++11 New World Order. For maximum
performance, you must think about move semantics. Simply saying const X&
everywhere will inhibit move semantics (can't move from const, can't move from
lvalues, especially can't move from lvalue-ref-to-const).

For example, suppose you're writing a FancyAppend() function, that will return
"LhsString, RhsString". Following your guidance, the signature would be
"string FancyAppend(const string&, const string&)". While that definitely
avoids copying the inputs, it is not optimal. Providing additional overloads
for string&& parameters can be more efficient (if at least one input is a
modifiable rvalue, it can be appended to in-place, which can avoid additional
memory allocation if it happens to have sufficient capacity; for repeated
appends, this can avoid quadratic copying of elements). Indeed, this is what
string's operator+() does.

Note that working with rvalue references does require more understanding. For
example, the signature "string&& BadAppend(string&&, const string&)" is a
severe error (one that the Standardization Committee made early on, and
corrected before shipping).

~~~
AstralStorm
And if you support both const arguments, maybe whole or part of the function
can be declared constexpr. (or pure in C++17 or is it even 14)

~~~
StephanTLavavej
There's no such thing as "pure" in Standard C++ yet, even the C++17 Working
Paper.

~~~
AstralStorm
Isn't that specified as one of the attributes? Maybe it's just a common
extension.

------
partycoder
In theory you can achieve a lot in C++.

But C++ is like a large buffet where you can pick from a lot of different
abstractions.

Rust on the other hand is not as flexible. But it rather tries to focus in
safer abstractions, and provide convenience and coherence around them.

~~~
akiselev
_> Rust on the other hand is not as flexible._

How so? If you want the same flexibility as C++ memory safety wise, you just
use the unsafe keyword when you need to. A lot of Rust programmers are scared
of unsafe blocks for some reason but writing unsafe code in Rust is no less
safe than writing regular C/C++ except you can choose to reenable safety
whenever you want to. You have to look out for subtle bugs that come up when
safe code makes assumptions about unsafe code that you don't properly
implement but these are logic bugs, same as you'd find in any other language
when a module breaks a contract. Just don't use unsafe willy nilly in a
library crate that you expect other people to use.

As far as the rest of the language, I find Rust's traits to be far more
flexible than C++ OOP, it just takes a little while to adapt to thinking in
trait based composition. Once trait specialization and "impl Trait" hit stable
and the Rust team figures out how to make the ambiguity rules less
conservative, we'll have the best of both worlds: interfaces (both dynamic and
static), trait based composition, and inheritance (you can already do regular
OOP inheritance with a little bit of boilerplate like how Servo uses
Upcast<T>/Downcast<T> traits for HTML object hierarchies). Both are currently
in nightly (impl Trait might be beta already).

~~~
partycoder
"unsafe" is expressing that you are opting-in for less safe code. Same in C#.

Many unsafe abstractions are not opt-in in C++. You can go ahead and writing
them. No warning or cost.

"mut" and "unsafe" are keywords that you need to write down each time. They're
keywords you can spot easily during a code review and say "how do you justify
this?".

Rust's language design choice of having everything being immutable and safe by
default is a great idea.

~~~
Manishearth
> "unsafe" is expressing that you are opting-in for less safe code. Same in
> C#.

Yes, but that is scoped. You can write unsafe code when designing an
abstraction, and seal off the unsafety with a safe-to-use API. You then verify
the abstraction, and are free to use it however you want. This is what's
usually done, and this is what unsafe is _for_.

"mut" isn't really a red-flag keyword. Sure, you don't want unnecessary
mutation and everything is immutable by default, but nobody really has issues
with making things mutable. Rust is not a language where purity is important.

"unsafe" is, but for designing abstractions that's what unsafe is for, and
while you still should justify it, "I need an abstraction like this and it
can't be implemented in safe Rust because <reasons>" should be enough. Of
course, you should be able to explain why it is safe to use, preferably in the
docs/comments.

~~~
partycoder
No but "mut" comes with a maintenance cost. Where it is harder to make
assumptions over its value.

------
WhitneyLand
Why should "get_first_name_mut" not be simplified to "first_name"?

~~~
ycmbntrthrwaway
Mostly because Rust does not support overloading and you may want both mut and
non-mut versions.

------
AstralStorm
The proper way to avoid problems with use after move would be an instrumented
compiler adding a check. You can partially do it on your own, but it might be
more expensive.

(That by employing pointers or sentinels.)

