En Rust, cuando asignas un valor a otra variable, la propiedad se mueve y el original queda invalidado. Pero hay una excepción importante: los tipos que implementan el trait Copy se duplican de forma implícita en lugar de moverse. Para los demás tipos, puedes pedir una copia explícita con .clone(). Entender cuándo usar cada uno es clave para escribir código Rust idiomático.
El trait Copy
Copy marca tipos cuya duplicación es barata y segura: basta con copiar los bytes del stack. Rust lo hace automáticamente en las asignaciones.
let x: i32 = 42;
let y = x; // copia implícita
println!("{}", x); // OK: x sigue válido
println!("{}", y); // OK
Los tipos que implementan Copy por defecto en la stdlib:
- Todos los enteros:
i8i128,u8u128,isize,usize - Flotantes:
f32,f64 bool,char- Referencias inmutables:
&T(copiar un puntero es barato) - Tuplas y arrays cuyos elementos son todos
Copy
Tipos que no son Copy: String, Vec<T>, Box<T>, HashMap, referencias mutables &mut T.
Por qué String no es Copy
String tiene datos en el heap. Copiar los bytes del stack daría dos punteros al mismo dato, y cuando ambos salieran de scope intentarían liberar la misma memoria: double free. Rust lo impide no implementando Copy para tipos con recursos en el heap.
Derivar Copy en tus propios tipos
#[derive(Copy, Clone)]
struct Punto {
x: f64,
y: f64,
}
fn main() {
let p1 = Punto { x: 1.0, y: 2.0 };
let p2 = p1; // copia, no move
println!("p1: ({}, {})", p1.x, p1.y); // OK
println!("p2: ({}, {})", p2.x, p2.y); // OK
}
Solo puedes derivar Copy si todos los campos del tipo también son Copy. Si alguno contiene un String, no podrás.
Nota: Copy requiere que también implementes Clone. Por eso se derivan juntos.
El trait Clone
Clone es para duplicaciones explícitas y potencialmente caras. Se invoca con .clone():
let s1 = String::from("hola");
let s2 = s1.clone(); // duplica el contenido del heap
println!("{}", s1); // OK
println!("{}", s2); // OK
El hecho de que sea explícito es intencional: Rust quiere que seas consciente de las operaciones costosas. Si ves un .clone() en una revisión de código, sabes que hay una asignación de memoria implícita ahí.
Derivar Clone para tus structs
#[derive(Clone)]
struct Usuario {
nombre: String,
activo: bool,
}
fn main() {
let u1 = Usuario {
nombre: String::from("Ana"),
activo: true,
};
let u2 = u1.clone();
println!("{}", u1.nombre); // OK
println!("{}", u2.nombre); // OK
}
Cuándo usar Arc en lugar de clone
Si necesitas compartir un dato grande entre varios propietarios sin duplicarlo, usar .clone() repetidamente es ineficiente. La alternativa es Arc<T> (Atomic Reference Counting): un puntero inteligente que permite múltiples propietarios con un único dato en memoria.
use std::sync::Arc;
let datos = Arc::new(vec![1, 2, 3, 4, 5]);
let copia1 = Arc::clone(&datos); // solo copia el puntero
let copia2 = Arc::clone(&datos); // idem
// Todos apuntan al mismo Vec en memoria
println!("{:?}", datos);
println!("{:?}", copia1);
println!("{:?}", copia2);
Arc::clone incrementa un contador atómico, no copia el dato. Cuando el último Arc sale de scope, el dato se libera.
Resumen de la regla de decisión
| Situación | Qué usar |
|---|---|
| Tipo primitivo (i32, bool, char ) | Copy automático |
| Struct con todos campos Copy | #[derive(Copy, Clone)] |
| Necesitas duplicar un String o Vec | .clone() |
| Varios propietarios del mismo dato grande | Arc<T> |
Resumen
Copyes para tipos baratos de duplicar; la copia es implícita y automática.Clonees para duplicaciones explícitas, que pueden ser costosas.Stringy tipos con heap no sonCopypara evitar double free.- Cuando necesitas compartir sin duplicar, usa
Arc<T>.
El siguiente artículo cubre las dangling references: qué son, por qué Rust las detecta en compilación y cómo resolverlas con ownership o lifetime annotations.
