I have an issue with a simple struct creation with generic type, as follows:
struct Point3<T> {
pub x: T,
pub y: T,
pub z: T,
}
impl<T> Point3<T>
{
fn create3(vx: T, vy: T, vz: T) -> Point3<T> {
Point3::<T> {
x: vx,
y: vy,
z: vz,
}
}
fn create1(v: T) -> Point3<T> {
Point3::<T> {
x: v,
y: v,
z: v,
}
}
}
but when I tried to compile it, I got an error :
fn create1(v: T) -> Point3<T> {
| - move occurs because `v` has type `T`, which does not implement the `Copy` trait
34 | Point3::<T> {
35 | x: v,
| - value moved here
36 | y: v,
| ^ value used here after move
I understand than v
is moved in x
and so not available for y
or z
.
so it seems I need to copy it, but I don't know how to do that, nor implement the Copy
trait for T
since its a generic type
If I pass v
by ref I have another error
I'm sure it's pretty simple, but as a c/c dev rust is complex to me for the moment :)
CodePudding user response:
When working with type parameters, the only thing that Rust assumes about the type is that it is Sized
. Any other constraints on the type parameter must be explicitly written out.
Also, Rust's default move semantics mean that a value can have only a single owner, and that once a value is "moved out of" a place, it is no longer valid there. You can opt out of this behaviour by implementing Copy
, though this is only valid for types that can be meaningfully memcpy
-ed, so many things can't use this (anything with a Drop
impl, &mut T
, and more). TLDR, it's a fairly restrictive bound to put on an API.
The much more general case is Clone
, which is a supertrait of Copy
(which means: any T
that implements Copy
, also implements Clone
). The difference is that Clone
is potentially an expensive operation, potentially requiring heap allocations, and must be explicitly called via Clone::clone
, but the tradeoff is that it is far more widely applicable.
So I'd suggest rewriting your code as follows:
// this function doesn't need to impose any special bounds on T,
// since it never needs to be cloned
impl<T> Point3<T> {
fn create3(x: T, y: T, z: T) -> Self {
Self { x, y, z }
}
}
// this function does require clone, so we have to add the bound
impl<T: Clone> Point3<T> {
fn create1(v: T) -> Self {
Self {
x: v.clone(),
y: v.clone(),
z: v,
}
}
}
This allows your API to be used by significantly more types, but also doesn't add any performance overhead for Copy
types, since the Clone
implementation for a type that is also Copy
is basically a standard memcpy
.
Admittedly, with a struct called Point3
, it's probably only going to be used for numbers, so the Copy
vs Clone
distinction isn't very relevant, but in the general case, a Clone
bound is more widely usable.