Live data from Hacker News

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

nablag.com

61–70 of 128 posts

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

#61

> Granted, these repercussions of these defaults also result in (in my opinion) verbose language constructs like iter, into_iter, iter_mut ↩ Note that assuming the into_iter comes from IntoIterator that’s what the for loop invokes to get an iterator from an iterable. So for lr in LoadRequests.into_iter() { Is completely unnecessary verbosity, for lr in LoadRequests { Will do the exact same thing. And the stdlib will…

This is where I think linters can shine as educational tools. Underline either as an error and you’ve taught someone something that’s actually quite tricky to discover on your own.

Similar to all the times I defensively str(something) in Python to find that “oh that has __str__ called on it anyways.”

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

#62

Earlier quoted context omitted.

Well since you're saying "physically" I guess we should talk about a concrete thing, so lets say we're compiling this for the archaic Intel Core i7 I'm writing this on. 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 t…

True. When I wrote the comment I did not think about the Vec though. The point I am trying to make is more general: I believe that when you have a type in Rust that is not Copy it will never be implicitly copied in a way that you end up with two visible instances but it is not guaranteed that Rust never implicitly memcopies all its bytes. I have not tried it but what I had in mind instead of the Vec was a big struct…

Wouldn’t you need a Pin to guarantee no copying? I think copy has two different meanings, depending on whether you’re talking about the underlying memory representation and the logical representation that is available to the developer.

Obviously the distinction can matter sometimes and thus copy in the logical sense is a leaky abstraction (although in practice I notice I do not see that leakage often).

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

#63

Earlier quoted context omitted.

> The destructor already has to deal with that. That's partly true, partly circular. Because moves work this way, it's harder to make a class that doesn't have empty states, so I don't design my class to avoid empty states, so the destructor has to handle them.

Please give me an example for a class that needs to handle empty state in the destructor only because of move operations. These exist, but IME they are very rare. As soon as you have a default constructor, the destructor needs to handle the case of empty state.

A socket.

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

#64

Note that taking a 'const' by-value parameter is very sensible in some cases, so it is not something that could be detected as a typo by the C++ compiler in general.

Yes. For example, if an argument fits into the size of a register, it's better to pass by value to avoid the extra indirection.

> if an argument fits into the size of a register, it's better to pass by value to avoid the extra indirection.

Whether an argument is passed in a register or not is unfortunately much more nuanced than this: it depends on the ABI calling conventions (which vary depending on OS as well as CPU architecture). There are some examples where the argument will not be passed in a register despite being "small enough", and some examples where the argument may be split across two or more registers.

For instance, in the x86-64 ELF ABI spec [0], the type needs to be [0] Section 3.2.3 of https://gitlab.com/x86-psABIs/x86-64-ABI

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

#65

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.

C++ doesn't have ownership baked into the language like Rust does, and "move semantics" is all about ownership (under the hood it's just a plain old shallow copy both in C++ and Rust). Making the moved from object inaccessible like in Rust would have required static ownership tracking which I guess the C++ committee was afraid to commit to (and once you have that, you're basically halfway to Rust, including the downs…

You'd have to mark some functions as deleting their arguments. But I wouldn't really call that ownership. And it shouldn't restrict the language: If the compiler can't solve it statically then it can set a flag or null and check it before calling the destructor. Instead of a guard being built into every destructor use.

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

#66

Earlier quoted context omitted.

> The destructor already has to deal with that. That's partly true, partly circular. Because moves work this way, it's harder to make a class that doesn't have empty states, so I don't design my class to avoid empty states, so the destructor has to handle them.

Please give me an example for a class that needs to handle empty state in the destructor only because of move operations. These exist, but IME they are very rare. As soon as you have a default constructor, the destructor needs to handle the case of empty state.

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 place.

The moved-from value has to be valid after move (all of its invariants need to hold), which means you can’t express invariants unless they can survive a move.

It is much better for the language to simply zap the moved-from value out of existence so that you don’t have to deal with any of that.

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

#67

Earlier quoted context omitted.

True. When I wrote the comment I did not think about the Vec though. The point I am trying to make is more general: I believe that when you have a type in Rust that is not Copy it will never be implicitly copied in a way that you end up with two visible instances but it is not guaranteed that Rust never implicitly memcopies all its bytes. I have not tried it but what I had in mind instead of the Vec was a big struct…

Yes, Rust absolutely might memcpy your Big when you move it somewhere. I will say that programmers very often have bad instincts for when that's a bad idea. If you have a mix of abilities and can ask, try it, who in your team thinks that'll perform worse for moving M = 64 or M = 32? Don't give them hours to think about it. I would not even be surprised to find real world experienced programmers whose instinct tells t…

> I will say that programmers very often have bad instincts for when that's a bad idea

True that. memcpy is basically the literal fastest thing your processor can do, it’s trivially pipelined and can be done asynchronously.

If the alternative is heap storage you’re almost always cooked: that heap space is far less likely to be in L1 cache, allocating it takes time and requires walking a free list, dealing with memory fragmentation, freeing it when dropped, etc.

It’s not a bad short-hand to think of the heap as being 10-100x slower than the stack.

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

#68

Earlier quoted context omitted.

Please give me an example for a class that needs to handle empty state in the destructor only because of move operations. These exist, but IME they are very rare. As soon as you have a default constructor, the destructor needs to handle the case of empty state.

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, it only makes such requirements for standard library types, but not for user defined types. Please correct me if I'm wrong.

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

#69

Earlier quoted context omitted.

Please give me an example for a class that needs to handle empty state in the destructor only because of move operations. These exist, but IME they are very rare. As soon as you have a default constructor, the destructor needs to handle the case of empty state.

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 overhead :)

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

#70
post #63

Earlier quoted context omitted.

Please give me an example for a class that needs to handle empty state in the destructor only because of move operations. These exist, but IME they are very rare. As soon as you have a default constructor, the destructor needs to handle the case of empty state.

A socket.

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