Consider adding is_bitwise_copyable<T:Clone>() which return false if cloning values of type T matters.
Something similar could be achieved with impl specializations, by checking does T implements Copy. But that's not the same - Clone -able type may be bitwise-copyable too!
Isn't the standard way for a type to say "I can be bitwise copied" is for it to impl Copy? If such a type didn't implement copy, how would it signal to is_bitwise_copyable that it is, in fact, bitwise copyable?
No; it's possible that a type implements Copy, but its Clone impl still does funky things (like printing a message to stdout).
But, the compiler knows whether a given type derives Clone (and as such doesn't do odd things), or if it does implement Clone manually (and thus might or might not do odd things).
Some containers in stdlib, like Vec, have the "optimization" that uses Copy instead of Clone whenever applicable. This could theoretically break buggy code, but the intent of Clone has always been that if it breaks it's because the Clone impl is bad, not because Vec itself is bad.
So maybe the compiler could provide a special macro-like primitive derived!(impl Trait for Type), that works for all traits for which Rust provides a #[derive], and returns true if the impl was generated by a #[derive], returns false if it was written by hand, and emits a compiler error if the impl doesn't even exist.
If this existed, it could be possible to forgo calling Clone only if it was automatically derived. There may be interesting cases where it's important to know whether PartialEq, PartialOrd, Eq and Ord were automatically derived, as well.
However, it doesn't solve everything because the type may be a struct that contains, inside it, types that had custom impls.
A single derive macro can implement multiple traits, one of which could be used to mark types that used the macro (although types could lie by implementing such a marker trait manually). The standard library does this:
Sorry, I am still not understanding your use-case for is_bitwise_copyable. What's your use-case for this function? It kind of sounds like you want to know if a Clone impl is going to have side-effects, but I don't see how is_bitwise_copyable helps with that.
I suppose such a function could be useful to know if a non-Copy type is safe to use with std::intrinsics::copy.
I want to know, should I call type's clone, or copy_nonoverlapping is enough.
Concrete example here https://github.com/tower120/any_vec/blob/main/src/clone_type.rs#L22-L28 .
AnyVec is type erased Vec, and I store it's clone function. As optimization I want to eliminate clone_fn call, If type is Copy-able. The problem is that impls! is actually not able to check if type is copyable in generic context.
I assume, that in general, one ptr::copy_nonoverlapping over large chunk of memory is faster then multiple clone calls, even when types are known compile-time. (For my untyped case this is definitely true).
How would rustc know that a non-Copy type is safe to use with memcpy/ptr::copy?
I imagine in similar way as cpp do std::is_trivially_copyable, or as rust check if type Copy-compatible. Namely - empty Drop::drop, copy-compatible struct fields, etc...
That might not be quite sufficient. For example, perhaps a type which is Clone assigns a new unique id to each instance at the moment of cloning, for some reason — the internal id is Copy but shouldn't be copied.
I'd propose it should be defined as: may return true if the type is known to implement Copy, or if it has a derived Clone impl.
(However, this might run into the same soundness issues as specialization, and if we have specialization we don't need it.)
It's hard to accurately determine if a function and every function it calls has externally-observable side effects. Optimization does make use of this information, but the thing about optimization is that it is invisible to the program semantics.
Why may ? There could be false negatives with detecting if type Copy -able?
When I wrote that word I was thinking that it would allow the test to be possibly sound even when specialization isn't, by not guaranteeing the answer to be consistent, but I'm not sure.
Wait a minute - but need_drop works somehow. If it possible to detect Drop implementation, whats wrong with Copy?
Drop has extra rules; for example, this program doesn't compile:
struct Foo<T>(T);
impl<T> Drop for Foo<T> where T: 'static {
fn drop(&mut self) {}
}
error[E0367]: `Drop` impl requires `T: 'static` but the struct it is implemented for does not
--> src/lib.rs:3:34
|
3 | impl<T> Drop for Foo<T> where T: 'static {
| ^^^^^^^
|
note: the implementor must specify the same requirement
--> src/lib.rs:1:1
|
1 | struct Foo<T>(T);
| ^^^^^^^^^^^^^^^^^
I'm not an expert on the issues of specialization and I might be wrong, but I think this rule exists specifically so that things like needs_drop can be precise: either Foo<...> implements Drop regardless of its generic parameters, or it never implements Drop regardless of its generic parameters. But Copy does not have any such restriction.
Not necessarily, if is_bitwise_copyable is documented as "callers must have the same outward behavior when this returns false" — we don't consider changes in size_of::<Foo>() to be a breaking change by Foo, even though they're observable, unless Foo made promises about its representation.
This is right. You can always assume it's safe to copy instead of clone() if a type impls Copy, regardless of how exactly Clone is implemented. Trying to exclude manual impls of Clone, or even impls containing side effects, would be an anti-feature. There are valid reasons to manually impl Clone: the main one is #[derive] producing incorrect bounds, but there are other possibilities, e.g. you could have a side effect that only takes effect (or is only important) for types that are known to not be Copy. Any manual impls should still be subject to any sort of library-level* clone()-to-copy optimization, just as they are subject to the clone()-to-copy optimizations currently performed by Vec.
So this hypothetical function should simply be "is this type known to be Copy". I am not sure whether it's possible to implement that soundly under the current rustc.
* I do remember some debate about whether or not converting clones to copies is a valid optimization at a compiler level, but that's different.
If this is added, it should be needs_clone, like needs_drop.
drop_in_place
clone
trivial (trait)
T: Copy
T: Copy
trivial (const)
!needs_drop::<T>()
!needs_clone::<T>()
possible
T: ?Sized
T: Clone
needs_drop is an optimization hint only, and is allowed to spuriously return the safe default true. Similarly, needs_clone could spuriously return true, and is_trivially_copiable = !needs_clone may spuriously return false.
needs_drop exists, because Drop is unusable as a bound. A struct may contain fields with Drop, but not implement Drop itself.
With copying this is not an issue. A copyable field doesn't make its struct Copy. The Copy trait has to be explicitly implemented for the outer type, and can't be inferred from the structure. Otherwise it could undermine guarantees of the newtype pattern.
So I think Copy optimizations are best left to compiler smarts and specialization.
Here's an interesting note, though: the unsoundness of a Copy specialization is that it behaves as a superset of a sound T: Copy, but it's still a subset of "all fields are bitwise copyable" because even a lifetime conditional Copy impl requires all fields are Copy (potentially conditional on the lifetime). While it's still possible to exploit this unsoundness by externally relying on the side effects of Clone happening it seems impossible to exploit internally to T without itself specializing on a lifetime (...potentially hidden behind Self: Copy).
It's also an interesting observation that (in the Unpin case) the root cause of some T not being bitwise cloneäble is necessarily not that its fields aren't — all primitive types are — but that it adds some nontrivial logic on top of those primitive fields.