O que são Clone e Copy?
Em Rust, o sistema de ownership determina que cada valor tem um único dono. Quando você atribui um valor a outra variável ou passa como argumento, o valor é movido — o dono original perde acesso. Os traits Clone e Copy oferecem alternativas ao move:
Clone: permite criar uma cópia profunda (deep copy) de um valor, chamando.clone()explicitamente. Pode envolver alocação de memória e ser custoso.Copy: permite cópia implícita bitwise. Quando um tipo implementaCopy, atribuições e passagem de parâmetros copiam o valor automaticamente em vez de movê-lo.
A relação entre eles é hierárquica: Copy requer Clone. Todo tipo Copy é também Clone, mas nem todo tipo Clone é Copy.
Definição dos Traits
// Definido em std::clone
pub trait Clone: Sized {
fn clone(&self) -> Self;
// Método opcional com implementação padrão
fn clone_from(&mut self, source: &Self) {
*self = source.clone();
}
}
// Definido em std::marker
// Copy é um marker trait — não tem métodos
pub trait Copy: Clone { }
Copy é um marker trait: não define nenhum método. Sua presença simplesmente informa ao compilador que o tipo pode ser copiado bit a bit de forma segura.
Como Implementar: derive vs impl manual
Derive de Clone e Copy
Para tipos simples cujos campos já implementam os traits:
// Tipo que pode ser Clone e Copy (todos os campos são Copy)
#[derive(Debug, Clone, Copy)]
struct Ponto {
x: f64,
y: f64,
}
// Tipo que só pode ser Clone (String não é Copy)
#[derive(Debug, Clone)]
struct Pessoa {
nome: String,
idade: u32,
}
fn main() {
// Ponto é Copy — atribuição copia automaticamente
let p1 = Ponto { x: 1.0, y: 2.0 };
let p2 = p1; // p1 é copiado, não movido
println!("{:?}", p1); // OK! p1 ainda é válido
println!("{:?}", p2);
// Pessoa é Clone mas NÃO Copy — atribuição move
let pessoa1 = Pessoa {
nome: String::from("Ana"),
idade: 30,
};
let pessoa2 = pessoa1.clone(); // Clone explícito
// let pessoa3 = pessoa1; // Isso moveria pessoa1!
println!("{:?}", pessoa1); // OK, usamos clone
println!("{:?}", pessoa2);
}
Implementação manual de Clone
Quando você precisa de lógica personalizada na clonagem:
#[derive(Debug)]
struct Buffer {
dados: Vec<u8>,
posicao: usize,
}
impl Clone for Buffer {
fn clone(&self) -> Self {
// Ao clonar, reseta a posição para o início
Buffer {
dados: self.dados.clone(),
posicao: 0,
}
}
fn clone_from(&mut self, source: &Self) {
// Mais eficiente: reutiliza a alocação existente
self.dados.clone_from(&source.dados);
self.posicao = 0;
}
}
fn main() {
let buf1 = Buffer {
dados: vec![1, 2, 3, 4, 5],
posicao: 3,
};
let buf2 = buf1.clone();
println!("buf1 posição: {}", buf1.posicao); // 3
println!("buf2 posição: {}", buf2.posicao); // 0 (resetado)
}
Implementação manual de Copy
Copy é implementado sem corpo (marker trait), mas requer que Clone já esteja implementado:
#[derive(Debug)]
struct Cor {
r: u8,
g: u8,
b: u8,
}
impl Clone for Cor {
fn clone(&self) -> Self {
// Para tipos Copy, clone é idêntico à cópia bitwise
*self
}
}
impl Copy for Cor {}
fn main() {
let vermelho = Cor { r: 255, g: 0, b: 0 };
let copia = vermelho; // Cópia implícita (Copy)
println!("{:?}", vermelho); // OK — não foi movido
println!("{:?}", copia);
}
Exemplos Práticos
Exemplo 1: Restrições de Copy
Copy só pode ser implementado se todos os campos também forem Copy. Tipos que possuem heap allocation (como String, Vec, Box) não podem ser Copy:
// FUNCIONA — todos os campos são Copy
#[derive(Debug, Clone, Copy)]
struct Dimensao {
largura: u32,
altura: u32,
}
// FUNCIONA — enums simples podem ser Copy
#[derive(Debug, Clone, Copy)]
enum Direcao {
Norte,
Sul,
Leste,
Oeste,
}
// NÃO COMPILA — String não é Copy
// #[derive(Debug, Clone, Copy)]
// struct Nome {
// valor: String, // Erro! String não implementa Copy
// }
// NÃO COMPILA — Vec não é Copy
// #[derive(Debug, Clone, Copy)]
// struct Lista {
// itens: Vec<i32>, // Erro! Vec não implementa Copy
// }
fn main() {
let dim = Dimensao { largura: 800, altura: 600 };
let dim2 = dim;
println!("{:?} {:?}", dim, dim2);
let dir = Direcao::Norte;
let dir2 = dir;
println!("{:?} {:?}", dir, dir2);
}
Exemplo 2: Clone em coleções
fn main() {
// Vec implementa Clone (mas não Copy)
let numeros = vec![1, 2, 3, 4, 5];
let copia = numeros.clone();
println!("Original: {:?}", numeros);
println!("Cópia: {:?}", copia);
// HashMap implementa Clone
use std::collections::HashMap;
let mut mapa = HashMap::new();
mapa.insert("chave", 42);
let mapa2 = mapa.clone();
println!("{:?}", mapa2);
// String implementa Clone
let s1 = String::from("Rust Brasil");
let s2 = s1.clone();
println!("{} {}", s1, s2); // Ambos válidos
}
Exemplo 3: clone_from para eficiência
O método clone_from pode ser mais eficiente que clone porque reutiliza a memória já alocada:
fn main() {
let fonte = String::from("texto longo que precisa de alocação");
// Cria uma string com capacidade existente
let mut destino = String::with_capacity(100);
destino.push_str("conteúdo antigo");
// clone_from reutiliza a capacidade de destino
// Em vez de alocar nova memória, copia para a memória existente
destino.clone_from(&fonte);
println!("{}", destino); // "texto longo que precisa de alocação"
// Para Vec, clone_from também é otimizado
let origem = vec![1, 2, 3, 4, 5];
let mut alvo = Vec::with_capacity(100);
alvo.clone_from(&origem);
println!("{:?}", alvo);
}
Exemplo 4: Copy e closures
Tipos Copy simplificam o uso com closures:
#[derive(Debug, Clone, Copy)]
struct Config {
max_tentativas: u32,
timeout_ms: u64,
}
fn processar_com_config(config: Config) {
// Como Config é Copy, a closure captura uma cópia
let tentativa = move || {
println!("Max tentativas: {}", config.max_tentativas);
};
// config ainda é válido aqui!
println!("Timeout: {}ms", config.timeout_ms);
tentativa();
}
fn main() {
let config = Config {
max_tentativas: 3,
timeout_ms: 5000,
};
processar_com_config(config);
// config ainda é válido porque é Copy
println!("{:?}", config);
}
Exemplo 5: Padrão com Rc para tipos não-Copy compartilhados
Quando você precisa compartilhar dados que não são Copy:
use std::rc::Rc;
#[derive(Debug)]
struct DadosGrandes {
conteudo: Vec<u8>,
}
fn main() {
// Em vez de clonar dados grandes, compartilhe com Rc
let dados = Rc::new(DadosGrandes {
conteudo: vec![0; 1_000_000], // 1MB de dados
});
// Rc::clone é barato — incrementa apenas o contador
let ref1 = Rc::clone(&dados);
let ref2 = Rc::clone(&dados);
println!("Referências: {}", Rc::strong_count(&dados)); // 3
println!("Tamanho: {}", ref1.conteudo.len());
println!("Tamanho: {}", ref2.conteudo.len());
}
Padrões e Boas Práticas
Derive
Clonegenerosamente: A maioria dos tipos deveria implementarClone. Isso dá flexibilidade ao usuário do seu tipo.Derive
Copycom cautela: Só adicioneCopya tipos pequenos e baratos de copiar (como structs com campos numéricos ou enums simples). Tipos com heap allocation não podem serCopy.Cuidado com
.clone()em loops:clone()pode ser caro para tipos com heap allocation. Em hot paths, considere usar referências ouRc/Arc.Implemente
clone_fromquando relevante: Se seu tipo gerencia recursos (comoVecouString), uma implementação personalizada declone_frompode evitar realocações.Copymuda a semântica de atribuição: Adicionar ou removerCopyde um tipo é uma mudança de quebra (breaking change). Atribuições que antes moviam passam a copiar (ou vice-versa).Tipos genéricos e Clone: Use
T: Clonecomo bound quando sua função ou struct precisa clonar o tipo genérico. Não exijaCopya menos que seja realmente necessário.Move semântico como padrão: O sistema de move do Rust evita cópias acidentais. Só use
clone()quando você realmente precisa de duas cópias independentes do dado.
Veja Também
- Display e Debug — traits comumente derivados junto com Clone
- Default Trait — outro trait derivável para inicialização
- Eq e Ord — traits de comparação, frequentemente combinados com Clone e Copy
- Ownership e Borrowing — o sistema que torna Clone e Copy necessários
- Erro E0382: Uso Após Move — quando esquecer de clonar causa erro