¿Cuál es la diferencia entre Copiar y Clonar?

128

Este problema parece implicar que es solo un detalle de implementación ( memcpyvs ???), pero no puedo encontrar ninguna descripción explícita de las diferencias.

usuario12341234
fuente
El código fuente de Rust tiene una explicación relevante
duan

Respuestas:

115

Cloneestá diseñado para duplicaciones arbitrarias: una Cloneimplementación para un tipo Tpuede realizar operaciones arbitrariamente complicadas necesarias para crear un nuevo T. Es un rasgo normal (aparte de estar en el preludio), por lo que requiere que se use como un rasgo normal, con llamadas a métodos, etc.

El Copyrasgo representa valores que se pueden duplicar de forma segura a través de memcpy: cosas como reasignaciones y pasar un valor por argumento a una función son siempre memcpys, por lo que para los Copytipos, el compilador comprende que no necesita considerarlos como un movimiento .

huon
fuente
5
¿Puedo entender como Clonees una copia profunda y Copyes una instantánea?
Djvu
11
Cloneabre la posibilidad de que el tipo pueda hacer una copia profunda o superficial: "arbitrariamente complicado".
Poolie
85

La principal diferencia es que la clonación es explícita. La notación implícita significa mover para un no Copytipo.

// u8 implements Copy
let x: u8 = 123;
let y = x;
// x can still be used
println!("x={}, y={}", x, y);

// Vec<u8> implements Clone, but not Copy
let v: Vec<u8> = vec![1, 2, 3];
let w = v.clone();
//let w = v // This would *move* the value, rendering v unusable.

Por cierto, todos los Copytipos también deben serlo Clone. Sin embargo, ¡no están obligados a hacer lo mismo! Para sus propios tipos, .clone()puede ser un método arbitrario de su elección, mientras que la copia implícita siempre activará a memcpy, no la clone(&self)implementación.

mdup
fuente
1
¡Frio! Esto aclara una pregunta secundaria que tenía sobre si el rasgo Clonar proporciona copia implícita. Resulta que esa pregunta y esta estaban más relacionadas de lo que pensaba. ¡Gracias!
user12341234
En su primer ejemplo, suponga que desea ymover x, no una copia, como con su último ejemplo comentado w = v. ¿Cómo especificarías eso?
johnbakers
2
No puede, y no lo hace, porque Copyestá destinado a implementarse para tipos "baratos", como u8en el ejemplo. Si escribe un tipo bastante pesado, para el que cree que un movimiento es más eficiente que una copia, no lo haga implícito Copy. Tenga en cuenta que en el caso de u8, posiblemente no puede ser más eficiente con un movimiento, ya que bajo el capó probablemente al menos implicaría una copia de puntero, que ya es tan cara como una copia de u8, así que ¿para qué molestarse?
mdup
¿Significa esto que la presencia del Copyrasgo tiene un impacto en los alcances de vida implícitos de las variables? Si es así, creo que es digno de mención.
Brian Cain
7

Como ya se cubrió en otras respuestas:

  • Copy es implícito, económico y no se puede volver a implementar (memcpy).
  • Clone es explícito, puede ser caro y puede volver a implementarse arbitrariamente.

Lo que a veces falta en la discusión de Copyvs Clonees que también afecta cómo el compilador usa movimientos vs copias automáticas. Por ejemplo:

#[derive(Debug, Clone, Copy)]
pub struct PointCloneAndCopy {
    pub x: f64,
}

#[derive(Debug, Clone)]
pub struct PointCloneOnly {
    pub x: f64,
}

fn test_copy_and_clone() {
    let p1 = PointCloneAndCopy { x: 0. };
    let p2 = p1; // because type has `Copy`, it gets copied automatically.
    println!("{:?} {:?}", p1, p2);
}

fn test_clone_only() {
    let p1 = PointCloneOnly { x: 0. };
    let p2 = p1; // because type has no `Copy`, this is a move instead.
    println!("{:?} {:?}", p1, p2);
}

El primer ejemplo ( PointCloneAndCopy) funciona bien aquí debido a la copia implícita, pero el segundo ejemplo ( PointCloneOnly) produciría un error con un uso después de mover:

error[E0382]: borrow of moved value: `p1`
  --> src/lib.rs:20:27
   |
18 |     let p1 = PointCloneOnly { x: 0. };
   |         -- move occurs because `p1` has type `PointCloneOnly`, which does not implement the `Copy` trait
19 |     let p2 = p1;
   |              -- value moved here
20 |     println!("{:?} {:?}", p1, p2);
   |                           ^^ value borrowed here after move

Para evitar el movimiento implícito, podríamos llamar explícitamente let p2 = p1.clone();.

Esto puede plantear la pregunta de cómo forzar un movimiento de un tipo que implemente el rasgo Copiar. . Respuesta corta: no puede / no tiene sentido.

bluenote10
fuente
@Shepmaster Lo eliminé aunque lo encuentro mucho más legible porque contiene la codificación de colores agradable del compilador de Rust y me aseguré específicamente de que todas las palabras relevantes para la búsqueda también estén contenidas en el texto.
bluenote10