- Feature Name:
all_the_clones
- Start Date: 2017-08-28
- RFC PR: rust-lang/rfcs#2133
- Rust Issue: rust-lang/rust#44496
Summary
Add compiler-generated Clone
implementations for tuples and arrays with Clone
elements of all lengths.
Motivation
Currently, the Clone
trait for arrays and tuples is implemented using a macro in libcore, for tuples of size 11 or less and for Copy
arrays of size 32 or less. This breaks the uniformity of the language and annoys users.
Also, the compiler already implements Copy
for all arrays and tuples with all elements Copy
, which forces the compiler to provide an implementation for Copy
’s supertrait Clone
. There is no reason the compiler couldn’t provide Clone
impls for all arrays and tuples.
Guide-level explanation
Arrays and tuples of Clone
arrays are Clone
themselves. Cloning them clones all of their elements.
Reference-level explanation
Make clone
a lang-item, add the following trait rules to the compiler:
n number
T type
T: Clone
----------
[T; n]: Clone
T1,...,Tn types
T1: Clone, ..., Tn: Clone
----------
(T1, ..., Tn): Clone
And add the obvious implementations of Clone::clone
and Clone::clone_from
as MIR shim implementations, in the same manner as drop_in_place
. The implementations could also do a shallow copy if the type ends up being Copy
.
Remove the macro implementations in libcore. We still have macro implementations for other “derived” traits, such as PartialEq
, Hash
, etc.
Note that independently of this RFC, we’re adding builtin Clone
impls for all “scalar” types, most importantly fn pointer and fn item types (where manual impls are impossible in the foreseeable future because of higher-ranked types, e.g. for<'a> fn(SomeLocalStruct<'a>)
), which are already Copy
:
T fn pointer type
----------
T: Clone
T fn item type
----------
T: Clone
And just for completeness (these are perfectly done by an impl in Rust 1.19):
T int type | T uint type | T float type
----------
T: Clone
T type
----------
*const T: Clone
*mut T: Clone
T type
'a lifetime
----------
&'a T: Clone
----------
bool: Clone
char: Clone
!: Clone
This was considered a bug-fix (these types are all Copy
, so it’s easy to witness that they are Clone
).
Drawbacks
The MIR shims add complexity to the compiler. Along with the derive(Clone)
implementation in libsyntax
, we have 2 separate sets of implementations of Clone
.
Having Copy
and Clone
impls for all arrays and tuples, but not PartialEq
etc. impls, could be confusing to users.
Rationale and Alternatives
Even with all proposed expansions to Rust’s type-system, for consistency, the compiler needs to have at least some built-in Clone
implementations: the type for<'a> fn(Foo<'a>)
is Copy
for all user-defined types Foo
, but there is no way to implement Clone
, which is a supertrait of Copy
, for it (an impl<T> Clone for fn(T)
won’t match against the higher-ranked type).
The MIR shims for Clone
of arrays and tuples are actually pretty simple and don’t add much complexity after we have drop_in_place
and shims for Copy
types.
The array situation
In Rust 1.19, arrays are Clone
only if they are Copy
. This code does not compile:
fn main() {
let x = [Box::new(0)].clone(); //~ ERROR
println!("{:?}", x[0]);
}
The reason (I think) is that there is no good way to write a variable-length array expression in macros. This wouldn’t be fixed by the first iteration of const generics. Actually, this can be done using a for-loop (ArrayVec
is used here instead of a manual panic guard for simplicity, but it can be easily implemented given const generics).
impl<const n: usize; T: Clone> Clone for [T; n] {
fn clone(&self) -> Self {
unsafe {
let result : ArrayVec<Self> = ArrayVec::new();
for elem in (self as &[T]) {
result.push(elem.clone());
}
result.into_inner().unwrap()
}
}
}
OTOH, this means that making non-Copy
arrays Clone
is less of a bugfix and more of a new feature. It’s however a nice feature - [Box<u32>; 1]
not being Clone
is an annoying and seemingly-pointless edge case.
Implement Clone
only for Copy
types
As of Rust 1.19, the compiler does not have the Clone
implementations, which causes ICEs such as rust-lang/rust#25733 because Clone
is a supertrait of Copy
.
One alternative, which would solve ICEs while being conservative, would be to have compiler implementations for Clone
only for Copy
tuples of size 12+ and arrays, and maintain the libcore
macros for Clone
of tuples (in Rust 1.19, arrays are only Clone
if they are Copy
).
This would make the shims trivial (a Clone
implementation for a Copy
type is just a memcpy), and would not implement any features that are not needed.
When we get variadic generics, we could make all tuples with Clone
elements Clone
. When we get const generics, we could make all arrays with Clone
elements Clone
.
Use a MIR implementation of Clone
for all derived impls
The implementation on the other end of the conservative-radical end would be to use the MIR shims for all #[derive(Clone)]
implementations. This would increase uniformity by getting rid of the separate libsyntax
derived implementation. However:
-
We’ll still need the
#[derive_Clone]
hook in libsyntax, which would presumably result in an attribute that trait selection can see. That’s not a significant concern. -
The more annoying issue is that, as a workaround to trait matching being inductive, derived implementations are imperfect - see rust-lang/rust#26925. This means that we either have to solve that issue for
Clone
(which is dedicatedly non-trivial) or have some sort of type-checking for the generated MIR shims, both annoying options. -
A MIR shim implementation would also have to deal with edge cases such as
#[repr(packed)]
, which normal type-checking would handle for ordinaryderive
. I think drop glue already encounters all of these edge cases so we have to deal with them anyway.
Copy
and Clone
for closures
We could also add implementations of Copy
and Clone
to closures. That is RFC #2132 and should be discussed there.
Unresolved questions
See Alternatives.