Clone e Copy Traits em Rust

Guia completo sobre Clone e Copy em Rust: cópia profunda vs cópia bitwise, quando usar derive, restrições de Copy e exemplos práticos.

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 implementa Copy, 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

  1. Derive Clone generosamente: A maioria dos tipos deveria implementar Clone. Isso dá flexibilidade ao usuário do seu tipo.

  2. Derive Copy com cautela: Só adicione Copy a tipos pequenos e baratos de copiar (como structs com campos numéricos ou enums simples). Tipos com heap allocation não podem ser Copy.

  3. Cuidado com .clone() em loops: clone() pode ser caro para tipos com heap allocation. Em hot paths, considere usar referências ou Rc/Arc.

  4. Implemente clone_from quando relevante: Se seu tipo gerencia recursos (como Vec ou String), uma implementação personalizada de clone_from pode evitar realocações.

  5. Copy muda a semântica de atribuição: Adicionar ou remover Copy de um tipo é uma mudança de quebra (breaking change). Atribuições que antes moviam passam a copiar (ou vice-versa).

  6. Tipos genéricos e Clone: Use T: Clone como bound quando sua função ou struct precisa clonar o tipo genérico. Não exija Copy a menos que seja realmente necessário.

  7. 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