- Feature Name: pod
- Start Date: 2015-03-04
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
Split the Copy
trait in two traits: Pod
and Copy
. Pod
indicates that a
type can be cloned by just copying its bits ("memcpy
"), and the new Copy
indicates that the type should be implicitly copied instead of moved.
We have a guideline that says that iterators should not be implicitly copyable
(i.e. they shouldn't implement Copy
). The Range
struct, which is what
a..b
desugars to, is an iterator, and under this guideline it shouldn't be
Copy
, but this hurts ergonomics in other cases where it's not used as an
iterator, for example a struct that contains Range
can't be marked as Copy
,
because Range
is not Copy
.
This is a more general problem, if upstream decides that some type shouldn't be
Copy
, even if it could, then downstream can't make Copy
able structs/enums
that contain said type.
This is unnecessarily restrictive because the data doesn't need to be
implicitly copyable, the actual requirement is that the data should be
cloneable by just memcpy
ing it.
Implementing Pod
for a type is much less controversial than implementing
Copy
on it. This should result in more types marked as Pod
in libraries,
which in turn gives downstream users more control about what can be Copy
as
the only requirement is that the fields must be Pod
.
Cell
can be modified to have a Pod
bound instead of a Copy
one, this
would allow more types, like iterators, to be stored in a Cell
.
A Pod
marker trait will be added to the stdlib, and the Copy
trait will be
updated as follows:
/// Indicates that this type can be cloned by just copying its bits (`memcpy`)
#[lang = "pod"]
trait Pod: MarkerTrait {}
/// Indicates that this type will be implicitly copied instead of moved
#[lang = "copy"]
trait Copy: Pod {}
Update the compiler to enforce the following rules:
Pod
can only be implemented by structs/enums whose fields are alsoPod
.- Built-in/primitive types that were automatically considered
Copy
by the compiler (i8
,&T
,(A, B)
,[T; N]
, etc), will now be considered to be bothPod
andCopy
. Copy
restrictions will carry over toPod
, e.g. types with destructors can't implementPod
, norCopy
because of thetrait Copy: Pod
requirement.
To avoid breaking the world, the #[derive(Copy)]
syntax extension will be
updated to derive both Copy
and Pod
:
#[derive(Copy)]
struct Foo<T>(T);
// expands into
impl<T: Pod> Pod for Foo<T> {}
impl<T: Pod> Copy for Foo<T> {}
//^ note that the bound is `T: Pod` (not `T: Copy`!)
Also add a #[derive(Pod)]
extension to derive only Pod
.
Cell
will be change its T: Copy
bound to T: Pod
.
Cognitive load. Currently you have to think whether a type should be Clone
,
Copy+Clone
or neither (affine type). Now you have to choose between Clone
,
Pod+Clone
, Pod+Clone+Copy
or neither. (There are other combinations like
e.g Pod+Copy
, but you almost always want to add Clone
in there as well)
Don't do this, figure out how some other way to solve the Range
problem
(should it be Copy
?). And, don't let non-implictly copyable structs (like
most iterators) be stored in Cell
s.
To reduce the burden of having to write #[derive(Pod, Clone)]
and
#[derive(Copy, Clone)]
, the #[derive]
syntax extensions could be modified
as follows:
#[derive(Clone)]
derivesClone
#[derive(Pod)]
derivesPod
andClone
#[derive(Copy)]
derivesPod
,Clone
andCopy
This means that for any type you only have to pick one of these #[derive]
s
or neither.
The author would prefer to obtain the same behavior not with syntax extensions but with a blanket implementation:
impl Clone for T where T: Pod {
fn clone(&self) -> T {
unsafe {
mem::transmute_copy(self)
}
}
}
However, this is not actionable without negative bounds because of coherence issues (overlapping implementations).
Another option is to define the Pod
trait as an OIBIT. Pod
would then have
a default implementation, and negative implementations for mutable references
and for types with destructors
unsafe trait Pod: MarkerTrait {}
unsafe impl Pod for .. {}
impl<T: Drop> !Pod for T {}
impl<'a, T: ?Sized> !Pod for &'a mut T {}
The resulting behavior matches the built-in (compiler) rules of the current
Copy
trait.
As in the original proposal, the new Copy
trait would become a supertrait
over Pod
:
trait Copy: Pod {}
This approach has the advantage that is no longer necessary to manually
implement Pod
as the trait will be implicitly derived whenever the type can
be Pod
. And, when a type can't be Pod
, because e.g. one of its fields is
not Pod
, then it won't implicitly implement Pod
, however this can be
overridden with a manual unsafe impl Pod for Ty
.
The cases where one must explicitly opt-out of Pod
are rare, and involve
affine types without destructors. An example is iOS' OsRng
:
// not implictly copyable, doesn't implement `Drop`
#[cfg(target_os = "ios")]
struct OsRng {
_dummy: (),
}
// with this alternative, a negative implementation is required to maintain the
// current semantics
impl !Pod for OsRng {}
The downside of this approach is that by changing the fields of a struct the
Pod
ness of the type can be inadvertently (without compiler warning/error)
changed as well.
- Is there a name that better describes what
Pod
is? - Should we bring back the "you could implement
Pod
for type" lint? The author thinks that you almost always want to mark as many things as possible asPod
- that's not the case with the currentCopy
(e.g. iterators). - Should the
Pod
trait be in the prelude?