Live data from Hacker News

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

nablag.com

71–80 of 128 posts

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

#71

Earlier quoted context omitted.

It’s not just the destructor you have to worry about, it’s all of the state accessible to callers. If you have any type that represents validated data, say a string wrapper which conveys (say) a valid customer address, how do you empty it out? You could turn it into an empty string, but now that .street() method has to return an optional value, which defeats the purpose of your type representing validated data in the…

First, one shouldn't use a moved-from object in the first place (except for, maybe, reassigning it). Second, why can't the .street() method simply return an empty string in this case? > The moved-from value has to be valid after move (all of its invariants need to hold) The full quote from the C++ standard is: "Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state" AFAIK…

> First, one shouldn't use a moved-from object in the first place (except for, maybe, reassigning it).

It still requires you to come up with somethkng to do to the old value in the move constructor. What would you do in the ValidatedAddress case? Set a flag in the struct called “moved_from” and use that to throw an exception if it’s ever used? Wouldn’t it be nice if you just didn’t need to worry about it?

> Second, why can't the .street() method simply return an empty string in this case?

In this example I’m referring to a type that represents a “validated” address, so, one that has already passed checks to make sure the street isn’t empty, etc. (it’s the whole “parse, don’t validate” idea, although I’ve never understood why the word “parse” is used when I would’ve just called it “validate just once”.)

It is an extremely useful concept for your type system to represents invariants in your data like this. Having to make every type contain an “empty” case, just to make the language’s move semantics work, pokes an enormous hole through this idea.

> AFAIK, it only makes such requirements for standard library types, but not for user defined types

It makes the requirement because the compiler is not going to stop anyone from using the moved-from value, so you have to think of something to do in the move constructor. You can pinky-swear to never use the moved-from value in your own code (and linters can help here) but the possibility still exists, so it must be solved for.

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

#72

Earlier quoted context omitted.

This means C++ is riddled with types that have unrelated "I'm empty" state inside them rather than this being relegated to a separate wrapper type. It's Tony's Billion Dollar Mistake but smeared across an entire ecosystem. The smart pointer std::unique_ptr is an example of this, sometimes people will say it's basically a boxed T, so analogous to Rust's Box but it isn't quite, it's actually equivalent to Option >. And…

(And that difference leads to an ABI difference that makes it not a zero overhead abstraction in the way that Box is…)

Great point! Chandler Carruth explained this in one of this cppcon talks: https://youtu.be/rHIkrotSwcc?t=1047

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

#73

Earlier quoted context omitted.

First, one shouldn't use a moved-from object in the first place (except for, maybe, reassigning it). Second, why can't the .street() method simply return an empty string in this case? > The moved-from value has to be valid after move (all of its invariants need to hold) The full quote from the C++ standard is: "Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state" AFAIK…

> First, one shouldn't use a moved-from object in the first place (except for, maybe, reassigning it). It still requires you to come up with somethkng to do to the old value in the move constructor. What would you do in the ValidatedAddress case? Set a flag in the struct called “moved_from” and use that to throw an exception if it’s ever used? Wouldn’t it be nice if you just didn’t need to worry about it? > Second, w…

> Having to make every type contain an “empty” case, just to make the language’s move semantics work, pokes an enormous hole through this idea.

Nobody says that the invariants must hold after the object has been moved-from! The only thing you need to do is make sure that the destructor can run and do the right thing.

> You can pinky-swear to never use the moved-from value in your own code (and linters can help here) but the possibility still exists, so it must be solved for.

Letting the program crash would be a valid solution (for your own types).

For me the issue with C++ move semantics is not so much that you have to add special logic to your classes, but the fact that moved-from objects can be accessed in the first place. In this respect I definitely agree that destructive moves are better.

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

#74
post #63

Earlier quoted context omitted.

A socket.

How so? Doesn't your socket class have a default constructor and a notion of open and closed?

If the moves were destructive, I'd design it to have the default constructor call `::socket` and destructor call `::close`. And there wouldn't be any kind of "closed" state. Why would I want it?

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

#75
post #74

Earlier quoted context omitted.

How so? Doesn't your socket class have a default constructor and a notion of open and closed?

If the moves were destructive, I'd design it to have the default constructor call `::socket` and destructor call `::close`. And there wouldn't be any kind of "closed" state. Why would I want it?

Your socket class would have no default constructor? And you would never want to close the socket before the object's lifetime ends? Really?

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

#76

Earlier quoted context omitted.

> 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…

Now that would be a cool first proposal and implementation. I wonder if there’s any prior art in C++ yet.

If there is any prior art I'm not aware of it. The problems described in the part I snipped out around how destructive moves would work with class hierarchies sound thorny, for what it's worth.

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

#77
post #38

Earlier quoted context omitted.

Since I use move semantics all the time, this is for me the most frustrating thing about C++ full stop. I really wish they'd fix this instead of adding all those compile-time features.

How would you fix this in C++?

By adding syntax and semantics for destructible moves, meaning the moved object is removed from its scope (without calling its destructor.)

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

#78
post #47

Earlier quoted context omitted.

> The compiler isn't ignoring your new types True, I didn't meant to imply you can just ignore types; I meant to say that the equivalent operations on a naked vs wrapped value return equivalent assembly. It's one of those zero cost abstraction. You can writ your newtype wrapper and it will be just as if you wrote implementations by hand. > My favourite as a Unix person is Option . Yeah, but that's a bit different. Co…

You can't make your own types with niches (in stable Rust, yet, though I am trying to change that and I think there's a chance we'll make that happen some day) except for enumerations. So if you make an enumeration AlertLevel with values Ominous, Creepy, Terrifying, OMFuckingGoose then Option is a single byte, Rust will assign a bit pattern for AlertLevel::Ominous and AlertLevel::Creepy and so on, but the None just g…

> You can't make your own types with niches in stable Rust

You can, provided they are wrapper around NonZero types. See https://docs.rs/nonmax/latest/nonmax/

Hence my comment before NonZero types or Rust nightly.

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

#79

Earlier quoted context omitted.

This means C++ is riddled with types that have unrelated "I'm empty" state inside them rather than this being relegated to a separate wrapper type. It's Tony's Billion Dollar Mistake but smeared across an entire ecosystem. The smart pointer std::unique_ptr is an example of this, sometimes people will say it's basically a boxed T, so analogous to Rust's Box but it isn't quite, it's actually equivalent to Option >. And…

> This means C++ is riddled with types that have unrelated "I'm empty" state Again, these cases are still rare. Most classes either don't require user-defined move operations, or they have some notion of emptiness or default state. > And if we don't want to allow None? Too bad, you can't express that in C++ That's actually a good example! Nitpick: you can express it in C++, just not without additional logic and some…

>you can express it in C++, just not without additional logic and some overhead :)

How?

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

#80
post #74

Earlier quoted context omitted.

If the moves were destructive, I'd design it to have the default constructor call `::socket` and destructor call `::close`. And there wouldn't be any kind of "closed" state. Why would I want it?

Your socket class would have no default constructor? And you would never want to close the socket before the object's lifetime ends? Really?

With destructive moves, you can end an object's lifetime whenever you want.
Post reply on HN