Live data from Hacker News

The repercussions of missing an Ampersand in C++ and Rust

nablag.com

21–30 of 128 posts

Re: The repercussions of missing an Ampersand in C++ and Rust

#21

Great article. It think it raises a good point. An important aspect of modern programming languages should be to simplify the syntax, to help developers avoid mistakes. This reminds me of arguing more than once with JS developers about the dangers of loose typing (especially in the case of JS) and getting the inevitable reply ”I just keep track of my type casting.”.

I don't think the syntax has to be simple, it just needs to be expressive

Re: The repercussions of missing an Ampersand in C++ and Rust

#23
post #17
post #6

> I was specifically inspired by a performance bug due to a typo. This mistake is the “value param” vs “reference param” where your function copies a value instead of passing it by reference because an ampersand (&) was missing ... This simple typo is easy to miss the difference between `const Data& d` and `const Data d` isn't accurately characterized as "a typo" -- it's a semantically significant difference in inten…

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.

Re: The repercussions of missing an Ampersand in C++ and Rust

#24
post #4

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.

When I looked into the history of the C++ move (which after all didn't even exist in C++ 98 when the language was first standardized) I discovered that in fact they knew nobody wants this semantic. The proposal paper doesn't even try to hide that what programmers want is the destructive move (the thing Rust has) but it argues that was too hard to do with the existing C++ design so...

The more unfortunate, perhaps disingenuous part is that the proposal paper tries to pretend you can make the destructive move later if you need it once you've got their C++ move.

But actually what they're proposing is that "move + create" + "destroy" = "move". So, that's extra work it's not the same thing at all and sure enough in the real world this means extra work, from compilers, from programmers and sometimes (if it isn't removed by the optimiser) from the runtime program.

Re: The repercussions of missing an Ampersand in C++ and Rust

#25

Earlier quoted context omitted.

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.

When I looked into the history of the C++ move (which after all didn't even exist in C++ 98 when the language was first standardized) I discovered that in fact they knew nobody wants this semantic. The proposal paper doesn't even try to hide that what programmers want is the destructive move (the thing Rust has) but it argues that was too hard to do with the existing C++ design so... The more unfortunate, perhaps dis…

C++ is riddled with “good enough” without completeness. Resulting in more bandaids to the language to fix stuff they half implemented in the first place.

Re: The repercussions of missing an Ampersand in C++ and Rust

#27
As someone who programs both C++ and Rust, without even reading the article, my own experience with typos in those languages is:

Rust: Typo? Now it just doesn't compile anymore. Worst case is that the compiler does a bad job at explaining the error and you don't find it immediately.

C++: Typo? Good luck. Things may now be broken in so subtle and hard to figure out ways it may haunt you till the rest of your days.

But that of course depends on the nature of the typo. Now I should go and read the article.

Re: The repercussions of missing an Ampersand in C++ and Rust

#28
post #17

Earlier 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'm seeing this way too often in production code, despite linters and reviews. So we have to keep plastering over.

Re: The repercussions of missing an Ampersand in C++ and Rust

#29
This might be an unpopular opinion - I think const by-value parameters in C++ shouldn’t exist. Const reference and mutable values are enough for 99% cases, and the other 1% is r-value refs.

Regarding const by-value parameters, they should never appear in function declarations (without definition) since that doesn’t enforce anything. In function definitions, you can use const refs (which have lifetime extension) to achieve the same const-correctness, and const refs are better for large types.

Admittedly this further proves the point that c++ is needlessly complicated for users, and I agree with that.

Re: The repercussions of missing an Ampersand in C++ and Rust

#30
With 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) -> Owner {
      owner = Owner{data: d};
      // ...
      return owner
    }

Is this really true?

I believe in Rust, when you move a non-Copy type, like in this case, it is up to the compiler if it passes a reference or makes a physical copy.

In my (admittedly limited) understanding of Rust semantics calling

     FactoryFunction(d: Data) 
could physically copy d despite it being non-Copy. Is this correct?

EDIT:

Thinking about it, the example is probably watertight because d is essentially a Vec (as Ygg2 pointed out).

My point is that if you see

     FactoryFunction(d: Data) 
and all you know is that d is non-Copy you should not assume it is not physically copied on function call. At least that is my believe.
Post reply on HN