Rust's behavior of moving without leaving a moved-out shell behind also simplifies the implementation of the type itself, because its dtor doesn't have to handle the special case of a moved-out shell, and the type doesn't even need to be able to represent a moved-out shell. For example, a moved-out-from tree in C++ could represent this by having its inner root pointer be nullptr, and then its dtor would have to check…
This was one of the most unsatisfying things about learning C++ move semantics. They only kinda move the thing, leaving this shell behind is a nightmare.
The repercussions of missing an Ampersand in C++ and Rust
31–40 of 128 posts
Re: The repercussions of missing an Ampersand in C++ and Rust
#32Rust's behavior of moving without leaving a moved-out shell behind also simplifies the implementation of the type itself, because its dtor doesn't have to handle the special case of a moved-out shell, and the type doesn't even need to be able to represent a moved-out shell. For example, a moved-out-from tree in C++ could represent this by having its inner root pointer be nullptr, and then its dtor would have to check…
delete null is fine in C++ [1], so, assuming root either is a C++ object or a C type without members that point to data that also must be freed, its destructor can do delete root. And those assumptions would hold in ‘normal’ C++ code.
[1] https://en.cppreference.com/w/cpp/language/delete.html: “If ptr is a null pointer value, no destructors are called, and the deallocation function may or may not be called (it's unspecified), but the default deallocation functions are guaranteed to do nothing when passed a null pointer.”
Re: The repercussions of missing an Ampersand in C++ and Rust
#33With Rust executing a function for either case deploys the “optimal” version (reference or move) by default, moreover, the compiler (not the linter) will point out the any improper “use after moves”. struct Data { // Vec cannot implement "Copy" type data: Vec , } // Equivalent to "passing by const-ref" in C++ fn BusinessLogic(d :&Data) { d.DoThing(); } // Equivalent to "move" in C++ fn FactoryFunction(d: Data) -> Own…
If you have
Vec
// newtype struct
struct Data{ data: Vec }
// newtype enum in rust
// Possibly but not 100% sure
// enum OneVar { Data(Vec) }
From my experiments with newtype pattern, operations implemented on data and newtype struct yielded same assembly. To be fair in my case it wasn't a Vec but a [u8; 64] and a u32.Re: The repercussions of missing an Ampersand in C++ and Rust
#34Earlier quoted context omitted.
If the implications of a one char diff are this egregious that they’re considered obvious, maybe it should take less cognitive effort to spot this? CI and tooling are great, but would be far less necessary if it was more difficult to make this mistake in the first place.
What do you suggest? Some kind of std::const_reference ? Clang-tidy is enough in addition to the reviews.
I specifically take issue with this framing of it is not an issue for we have the tools to help with this, especially where the tools are not part of a standard distribution of a toolchain and require more than minimal effort. C++ has had many a warts for many decades, and the response has always been *you are just holding it wrong* and not running a well covering integration test suite with sanitizers on every commit, you just need to run one more tool in the CI, just a comprehensive benchmarking suite, have more eyes looking for a single char difference in reviews.
Re: The repercussions of missing an Ampersand in C++ and Rust
#35With Rust executing a function for either case deploys the “optimal” version (reference or move) by default, moreover, the compiler (not the linter) will point out the any improper “use after moves”. struct Data { // Vec cannot implement "Copy" type data: Vec , } // Equivalent to "passing by const-ref" in C++ fn BusinessLogic(d :&Data) { d.DoThing(); } // Equivalent to "move" in C++ fn FactoryFunction(d: Data) -> Own…
I believe the answer is technically yes. IIRC a "move" in Rust is defined as a bitwise copy of whatever is being moved, modulo optimizations. The only difference is what you can do with the source after - for non-Copy types, the source is no longer considered accessible/usable. With Copy types, the source is still accessible/usable.
Re: The repercussions of missing an Ampersand in C++ and Rust
#36With Rust executing a function for either case deploys the “optimal” version (reference or move) by default, moreover, the compiler (not the linter) will point out the any improper “use after moves”. struct Data { // Vec cannot implement "Copy" type data: Vec , } // Equivalent to "passing by const-ref" in C++ fn BusinessLogic(d :&Data) { d.DoThing(); } // Equivalent to "move" in C++ fn FactoryFunction(d: Data) -> Own…
On that machine Data is "physically" just the Vec, which is three 64-bit values, a pointer to i32 ("physically" on this machine a virtual address), an integer length and an integer capacity, and the machine has a whole bunch of GPRs so sure, one way the compiler might implement FactoryFuncton is to "physically" copy those three values into CPU registers. Maybe say RAX, RCX, RDX ?
Actually though there's an excellent chance that this gets inlined in your program, and so FactoryFunction never really exists as a distinct function, the compiler just stamps out the appropriate stuff in line every time we "call" this function, so then there was never a "parameter" because there was never a "function".
Re: The repercussions of missing an Ampersand in C++ and Rust
#37If you care about performance, you measure it. If you don't measure performance, you don't care about it.
Re: The repercussions of missing an Ampersand in C++ and Rust
#38Rust's behavior of moving without leaving a moved-out shell behind also simplifies the implementation of the type itself, because its dtor doesn't have to handle the special case of a moved-out shell, and the type doesn't even need to be able to represent a moved-out shell. For example, a moved-out-from tree in C++ could represent this by having its inner root pointer be nullptr, and then its dtor would have to check…
This was one of the most unsatisfying things about learning C++ move semantics. They only kinda move the thing, leaving this shell behind is a nightmare.
Re: The repercussions of missing an Ampersand in C++ and Rust
#39With Rust executing a function for either case deploys the “optimal” version (reference or move) by default, moreover, the compiler (not the linter) will point out the any improper “use after moves”. struct Data { // Vec cannot implement "Copy" type data: Vec , } // Equivalent to "passing by const-ref" in C++ fn BusinessLogic(d :&Data) { d.DoThing(); } // Equivalent to "move" in C++ fn FactoryFunction(d: Data) -> Own…
Can't run Godbolt on my phone for some reason, but in this case I expect compiler to ignore wrapper types and just pass Vec around. If you have Vec // newtype struct struct Data{ data: Vec } // newtype enum in rust // Possibly but not 100% sure // enum OneVar { Data(Vec ) } From my experiments with newtype pattern, operations implemented on data and newtype struct yielded same assembly. To be fair in my case it wasn'…
My favourite as a Unix person is Option. In a way Option is the same as the classic C int file descriptor. It has the exact same representation, 32 bits of aligned integer. But Rust's type system means we know None isn't a file descriptor, whereas it's too easy for the C programmer to forget that -1 isn't a valid file descriptor. Likewise the Rust programmer can't mistakenly do arithmetic on file descriptors, if we intend to count up some file descriptors but instead sum them in C that compiles and isn't what you wanted, in Rust it won't compile.
Re: The repercussions of missing an Ampersand in C++ and Rust
#40I would never have this typo as I usually delete the copy constructor in heavy structures.
this is the defensive and correct C++ approach, anyways.
If people could keep entirety of J.2 appendix in their mind at all time we would not have these issues. And if they had entirety of J appendix in mind all C code would be portable.
Or if people just always ran -Wall -Wpedantic -Wall_for_real_this_time -fsanitize=thread,memory,address,leaks,prayers,hopes,dreams,eldritch_beings,elder_gods -fno-omit-frame-pointer
I mean if this was all it took then C and C++ programs would be as safe as Rust. Which is not what we see in practice. And it's not like C programmers are an average web dev. It's a relatively niche and well versed community.