-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Copy clone semantics #1521
Copy clone semantics #1521
Conversation
Note that these comments were originally line comments, but that fact seems to have been lost by github. |
I originally thought this was a good idea, and even suggested something very much like this on the internals mailing list, but I've changed my mind. Every type that implements |
@briansmith The issue isn't derived The optimizer is also not turned on in debug mode. Allowing us to turn Edit: not invalid like undefined behavior or anything, just to be clear. |
Note, we've already made breaking changes based on this assumption. The change in |
IMO, it is OK to say that if you want
Yes, but IMO that was also a short-term mistake. Once the optimizer is doing the right thing, that shortcut won't be necessary and we can return to the simpler semantics. In particular, the optimizer can already notice that a loop is equivalent to memcpy. It just needs to get smarter to recognize non-loop equivalents, including recursive invocations of cone/copy, are equivalent to memcpy are equivalent to memcpy too. Consider |
@briansmith This isn't an optimization. This is specification. It's saying that the standard library (and anyone else) may rely on the fact that a properly implemented |
Yes, but I'm saying that if the optimizer is working well, then you don't need to specify anything special here. It would be useful to see an example where this rule is needed that can't be done without the rule by simply improving the optimizer. |
The RFC should specify what the consequences are for types that violate this rule. In particular this must not result in memory unsafety. |
@briansmith The issue isn't where the optimizer is working. It's where it isn't. Optimization is not always on, like in debug mode. Also, building a language for a sufficiently smart compiler doesn't seem like the correct way of doing things. Often we will have to specifically specialize. |
@Amanieu yes, that is the intention, I will write it down. |
Why not specify that even in debug mode, the copy coalescing optimizations are done? Then there is no semantic change to the language needed and debug build results are faster. |
@briansmith The semantics would still change, and then you would have different semantics between debug and release. |
To be clear, there's nothing that would change semantically in my definition. The only thing that would change is that debug builds would be specified to have an optimization pass enabled, for at least |
@briansmith Ah. You can't really do that. The necessary optimizations to realize that:
is equivalent to
is... most of them. |
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
What the exact wording should be. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternative wording: "When Copy
type has user-defined implementation of Clone
it's unspecified whether clone
or memcpy
is called when cloning a value of this type." or (shamelessly stolen from somewhere) "<...if Copy
...> An implementation is allowed to omit the call to clone
even if is has side effects. Such elision of calls to clone
is called clone elision."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@petrochenkov it's not that it's unspecified whether clone
or ptr::read
is called when you actually call .clone()
. It's just that libraries (including the standard library) are able to assume that Clone::clone
== ptr::read
for T: Copy
; and, if that requirement is not met, libraries are allowed to exhibit unexpected (but defined) behavior. (Just like if an Ord
type doesn't actually have an absolute ordering, or if an Eq
type doesn't actually have transitive equality).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not that it's unspecified whether clone or ptr::read is called when you actually call .clone()
Why not? If we go this route anyway, let's give the compiler/libraries all the freedom.
I'd even write "user-defined implementations of Clone
for Copy
types are ignored", but I'm not sure about generics:
fn f<T: Clone>(arg: T) -> T { arg.clone() } // It's not known if `T` is `Copy` or not and I'm not sure it's convenient to turn `clone`s into `memcpy`s during monomorphisation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just that libraries (including the standard library) are able to assume that Clone::clone == ptr::read for T: Copy
derive
is not a library, so the language already can omit clone
s as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, "clone
is always ignored" is better than "clone
is sometimes ignored" from pedagogical point of view.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm...
At first I thought this was a bad idea, but then I started thinking. Not "always ignored", but if your clone
has different semantics from a ptr::read
then it should be implementation defined whether it is called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't like the "optimizer magic overriding of clone
".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@arielb1 Yeah, I guess. I do think it would be better if it's just kept to libraries and language constructs like #[derive]
Is there any reason to write a manual |
@glaebhoerl Unfortunately, derived impls on generic structs sometimes have the wrong trait bounds, so you have to implement them manually. |
@comex: isn't that "just a bug"? |
Also, it's possible to implement manually, so it's possible to implement manually wrong. This RFC isn't about all those people who |
@ubsan I manually implement |
Some people do have to implement manually, like @retep998. |
I am not sure how much teeth this RFC has. We already have both |
@arielb1 Yes, because it's specified that |
Right. I think the "equivalent in functionally" language fits this case quite well. |
I think that the RFC as written follows what you're saying? |
We did not yet make the |
@bluss |
@ubsan My point was not that there should be As in, if |
I see this as pretty similar to the situation with |
🔔 This RFC is now entering its week-long final comment period 🔔 |
Hate breaking changes. Have you checked how many cargo crates will be affected? If yes - please share the numbers. From discussion it looks like breaking change just to make debug mode faster, but I think it can't be true. |
@e-oz This won't cause anything to stop compiling. This merely makes an assumption that the standard library and compiler can optimize based on. If you can find any crates in the wild that would be affected negatively by this change, please do bring them up as there isn't really any way to find out in an automated fashion. |
I think it is very unlikely that such a change will break anything at all. |
(I also don't consider this a change, merely formally stating an existing assumption.) Pre-emptive update: I don't really want to get into dickering over what is or is not a breaking change. What I meant here was that there is surely existing code that would exhibit bugs if clone and copy are not aligned (think generics), and that we've used the assumption that copy and clone align in their semantics elsewhere, but failed to document that assumption. |
Committed code contains words "this is a breaking change", that's why I asked. Sorry for being suspicious:) |
I'm in favor of documenting these expectations for the semantics of |
Huzzah! The @rust-lang/lang team has decided to accept this RFC. However, since the libs team also claims jurisdiction, I'll let them merge it. :) |
The libs team discussed this RFC at triage today and the decision was to merge. It was brought up that the user-facing documentation probably wants to indicate Thanks again for the RFC @ubsan! Tracking issue: rust-lang/rust#33416 |
Vec::clone specialization for T: Copy is realized since rust-lang/rust/pull/38182 (Through |
Specifying that
<T as Clone>::clone(&t)
whereT: Copy
should be equivalent toptr::read(&t)
.