Prototype (Clone) em Rust

O padrao Prototype em Rust: Clone trait em profundidade, Clone vs Copy, implementacoes customizadas, derive Clone e exemplos praticos de spawn de entidades em jogos.

Introducao

O Prototype (Prototipo) e um padrao criacional que permite criar novos objetos clonando uma instancia existente, em vez de construi-los do zero. Em Rust, esse padrao esta profundamente integrado na linguagem atraves da trait Clone, que faz parte da biblioteca padrao.

Enquanto em linguagens como Java voce precisa implementar Cloneable e lidar com CloneNotSupportedException, em Rust o Clone e uma parte natural e segura do sistema de tipos. O compilador pode inclusive derivar Clone automaticamente quando todos os campos de uma struct implementam Clone.


Problema

Voce esta desenvolvendo um jogo onde monstros surgem com base em templates predefinidos. Cada template define atributos base (vida, ataque, defesa, habilidades), mas cada instancia precisa de variacoes unicas (posicao, identificador, modificadores aleatorios).

Sem o padrao Prototype, voce precisaria reconstruir cada entidade do zero:

// Tedioso e propenso a erros: recriando tudo manualmente
fn criar_goblin(posicao: (f64, f64)) -> Monstro {
    Monstro {
        nome: "Goblin".to_string(),
        vida: 50,
        vida_maxima: 50,
        ataque: 10,
        defesa: 5,
        velocidade: 1.5,
        habilidades: vec![
            Habilidade::new("Golpe", 8, TipoHabilidade::Fisico),
            Habilidade::new("Fuga", 0, TipoHabilidade::Especial),
        ],
        resistencias: HashMap::from([
            (Elemento::Fogo, -0.2),  // fraco contra fogo
            (Elemento::Terra, 0.1),  // leve resistencia a terra
        ]),
        posicao,
        id: gerar_id_unico(),
        // ... e mais 10 campos que precisamos repetir
    }
}

Se tiver 50 tipos de monstros, isso se torna impossivel de manter.


Solucao em Rust

Clone: O Prototype Nativo de Rust

use std::collections::HashMap;

/// Identificador unico para cada entidade
static mut PROXIMO_ID: u64 = 0;

fn gerar_id() -> u64 {
    // Em producao, use AtomicU64
    unsafe {
        PROXIMO_ID += 1;
        PROXIMO_ID
    }
}

/// Tipos de elemento para dano e resistencia
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Elemento {
    Fogo,
    Agua,
    Terra,
    Ar,
    Trevas,
    Luz,
}

/// Tipo de habilidade
#[derive(Debug, Clone, Copy)]
pub enum TipoHabilidade {
    Fisico,
    Magico,
    Especial,
}

/// Uma habilidade que a entidade pode usar
#[derive(Debug, Clone)]
pub struct Habilidade {
    nome: String,
    dano_base: i32,
    tipo: TipoHabilidade,
    cooldown_turnos: u32,
    descricao: String,
}

impl Habilidade {
    pub fn new(nome: &str, dano: i32, tipo: TipoHabilidade) -> Self {
        Self {
            nome: nome.to_string(),
            dano_base: dano,
            tipo,
            cooldown_turnos: 1,
            descricao: String::new(),
        }
    }

    pub fn com_cooldown(mut self, turnos: u32) -> Self {
        self.cooldown_turnos = turnos;
        self
    }

    pub fn com_descricao(mut self, desc: &str) -> Self {
        self.descricao = desc.to_string();
        self
    }
}

/// Entidade do jogo que serve como prototipo
#[derive(Debug, Clone)]
pub struct Entidade {
    id: u64,
    nome: String,
    nivel: u32,
    vida: i32,
    vida_maxima: i32,
    ataque: i32,
    defesa: i32,
    velocidade: f64,
    habilidades: Vec<Habilidade>,
    resistencias: HashMap<Elemento, f64>,
    posicao: (f64, f64),
    experiencia_dada: u64,
}

impl Entidade {
    /// Cria uma copia do prototipo com novo ID e posicao
    pub fn spawn(&self, posicao: (f64, f64)) -> Self {
        let mut clone = self.clone();
        clone.id = gerar_id();
        clone.posicao = posicao;
        clone
    }

    /// Cria uma versao mais forte do prototipo
    pub fn spawn_elite(&self, posicao: (f64, f64)) -> Self {
        let mut clone = self.spawn(posicao);
        clone.nome = format!("{} Elite", clone.nome);
        clone.vida_maxima = (clone.vida_maxima as f64 * 1.5) as i32;
        clone.vida = clone.vida_maxima;
        clone.ataque = (clone.ataque as f64 * 1.3) as i32;
        clone.defesa = (clone.defesa as f64 * 1.2) as i32;
        clone.experiencia_dada = (clone.experiencia_dada as f64 * 2.0) as u64;
        clone.nivel += 5;
        clone
    }

    pub fn resumo(&self) -> String {
        format!(
            "[#{}] {} (Nv.{}) | HP: {}/{} | ATK: {} | DEF: {} | Pos: ({:.1}, {:.1})",
            self.id,
            self.nome,
            self.nivel,
            self.vida,
            self.vida_maxima,
            self.ataque,
            self.defesa,
            self.posicao.0,
            self.posicao.1,
        )
    }
}

/// Registro de prototipos (templates de entidades)
pub struct RegistroPrototipos {
    prototipos: HashMap<String, Entidade>,
}

impl RegistroPrototipos {
    pub fn new() -> Self {
        Self {
            prototipos: HashMap::new(),
        }
    }

    /// Registra um prototipo com um nome
    pub fn registrar(&mut self, chave: &str, prototipo: Entidade) {
        self.prototipos.insert(chave.to_string(), prototipo);
    }

    /// Cria uma nova instancia a partir do prototipo
    pub fn spawn(&self, chave: &str, posicao: (f64, f64)) -> Option<Entidade> {
        self.prototipos.get(chave).map(|p| p.spawn(posicao))
    }

    /// Cria uma versao elite do prototipo
    pub fn spawn_elite(&self, chave: &str, posicao: (f64, f64)) -> Option<Entidade> {
        self.prototipos.get(chave).map(|p| p.spawn_elite(posicao))
    }

    /// Lista todos os prototipos disponiveis
    pub fn listar(&self) -> Vec<&str> {
        self.prototipos.keys().map(|k| k.as_str()).collect()
    }
}

fn main() {
    let mut registro = RegistroPrototipos::new();

    // Define prototipos (templates)
    registro.registrar(
        "goblin",
        Entidade {
            id: 0,
            nome: "Goblin".to_string(),
            nivel: 3,
            vida: 50,
            vida_maxima: 50,
            ataque: 12,
            defesa: 5,
            velocidade: 1.5,
            habilidades: vec![
                Habilidade::new("Golpe Rapido", 8, TipoHabilidade::Fisico),
                Habilidade::new("Fuga", 0, TipoHabilidade::Especial),
            ],
            resistencias: HashMap::from([
                (Elemento::Terra, 0.1),
                (Elemento::Fogo, -0.2),
            ]),
            posicao: (0.0, 0.0),
            experiencia_dada: 30,
        },
    );

    registro.registrar(
        "dragao",
        Entidade {
            id: 0,
            nome: "Dragao Vermelho".to_string(),
            nivel: 50,
            vida: 5000,
            vida_maxima: 5000,
            ataque: 150,
            defesa: 80,
            velocidade: 0.8,
            habilidades: vec![
                Habilidade::new("Sopro de Fogo", 200, TipoHabilidade::Magico)
                    .com_cooldown(3)
                    .com_descricao("Cone de chamas devastador"),
                Habilidade::new("Golpe de Cauda", 80, TipoHabilidade::Fisico),
                Habilidade::new("Voo", 0, TipoHabilidade::Especial),
            ],
            resistencias: HashMap::from([
                (Elemento::Fogo, 0.9),
                (Elemento::Agua, -0.5),
                (Elemento::Ar, 0.3),
            ]),
            posicao: (0.0, 0.0),
            experiencia_dada: 5000,
        },
    );

    // Spawna multiplos goblins em posicoes diferentes (todos clonados do prototipo)
    println!("=== Spawning Goblins ===");
    let posicoes_goblins = vec![(10.0, 20.0), (15.0, 25.0), (8.0, 30.0)];
    for pos in posicoes_goblins {
        let goblin = registro.spawn("goblin", pos).unwrap();
        println!("{}", goblin.resumo());
    }

    // Spawna um goblin elite
    println!("\n=== Spawning Goblin Elite ===");
    let goblin_elite = registro.spawn_elite("goblin", (50.0, 50.0)).unwrap();
    println!("{}", goblin_elite.resumo());

    // Spawna um dragao
    println!("\n=== Spawning Dragao ===");
    let dragao = registro.spawn("dragao", (100.0, 100.0)).unwrap();
    println!("{}", dragao.resumo());
    println!(
        "  Habilidades: {:?}",
        dragao.habilidades.iter().map(|h| &h.nome).collect::<Vec<_>>()
    );
}

Clone vs Copy: Entendendo a Diferenca

/// Copy: copia bit a bit, automatica, para tipos pequenos e simples
/// - Implementado para: i32, f64, bool, char, tuplas de Copy, etc.
/// - A copia acontece IMPLICITAMENTE
#[derive(Debug, Clone, Copy)]
struct Ponto {
    x: f64,
    y: f64,
}

/// Clone: copia explicita, pode envolver alocacoes no heap
/// - Implementado para: String, Vec, HashMap, etc.
/// - A copia requer chamada EXPLICITA de .clone()
#[derive(Debug, Clone)]
struct Jogador {
    nome: String,          // String esta no heap - precisa de Clone, nao Copy
    posicao: Ponto,        // Ponto e Copy
    inventario: Vec<Item>, // Vec esta no heap - precisa de Clone
}

#[derive(Debug, Clone)]
struct Item {
    nome: String,
    peso: f64,
}

fn demonstrar_diferenca() {
    // Copy: implicito, o original continua valido
    let p1 = Ponto { x: 1.0, y: 2.0 };
    let p2 = p1; // copia implicitamente
    println!("p1 = {:?}, p2 = {:?}", p1, p2); // ambos validos!

    // Clone: explicito, cria nova alocacao no heap
    let j1 = Jogador {
        nome: "Maria".to_string(),
        posicao: Ponto { x: 0.0, y: 0.0 },
        inventario: vec![
            Item { nome: "Espada".to_string(), peso: 3.5 },
        ],
    };
    let j2 = j1.clone(); // precisa chamar .clone() explicitamente
    // let j3 = j1; // isso MOVE j1, nao copia!
    println!("j2.nome = {}", j2.nome);
}

Diagrama

PADRAO PROTOTYPE:

    +-----------------------+
    | RegistroPrototipos    |
    |                       |
    | "goblin"  -> Entidade |----.clone()---> Goblin #1 (pos: 10,20)
    |                       |----.clone()---> Goblin #2 (pos: 15,25)
    |                       |----.clone()---> Goblin #3 (pos: 8,30)
    |                       |
    | "dragao"  -> Entidade |----.clone()---> Dragao #4 (pos: 100,100)
    |                       |
    | "esqueleto" -> ...    |
    +-----------------------+

    O prototipo original nunca e consumido.
    Cada clone recebe um ID unico e posicao propria.


CLONE vs COPY:

    Copy (bit a bit, stack):       Clone (pode alocar heap):
    +-------+    bit copy   +-------+    +-------+   deep copy  +-------+
    | x: 1  | -----------> | x: 1  |    | ptr --|-> | ptr --|-> "Maria"
    | y: 2  |              | y: 2  |    | len:5 |   | len:5 |
    +-------+              +-------+    +-------+   +-------+
     (implicito)                         (.clone() explicito)

Exemplo do Mundo Real

Sistema de templates de documentos onde novos documentos sao criados a partir de modelos:

use std::collections::HashMap;

/// Tipo de conteudo de um bloco do documento
#[derive(Debug, Clone)]
pub enum ConteudoBloco {
    Titulo(String),
    Paragrafo(String),
    Codigo { linguagem: String, conteudo: String },
    Imagem { url: String, legenda: String },
    Tabela { cabecalho: Vec<String>, linhas: Vec<Vec<String>> },
    Separador,
}

/// Metadados do documento
#[derive(Debug, Clone)]
pub struct Metadados {
    pub autor: String,
    pub tags: Vec<String>,
    pub propriedades: HashMap<String, String>,
}

/// Documento completo que pode servir como prototipo
#[derive(Debug, Clone)]
pub struct Documento {
    pub titulo: String,
    pub metadados: Metadados,
    pub blocos: Vec<ConteudoBloco>,
    pub criado_em: String,
    pub versao: u32,
}

impl Documento {
    /// Cria uma copia do documento como novo rascunho
    pub fn criar_rascunho(&self, novo_titulo: &str, autor: &str) -> Self {
        let mut doc = self.clone();
        doc.titulo = novo_titulo.to_string();
        doc.metadados.autor = autor.to_string();
        doc.criado_em = "2025-01-15".to_string(); // simulacao
        doc.versao = 1;
        doc
    }
}

/// Clone customizado: quando voce precisa de logica especial
#[derive(Debug)]
pub struct ConexaoPool {
    pub nome: String,
    pub max_conexoes: u32,
    conexoes_ativas: u32, // NAO deve ser clonado com o valor atual
    id_interno: u64,      // Cada clone precisa de ID unico
}

impl Clone for ConexaoPool {
    fn clone(&self) -> Self {
        static mut CONTADOR: u64 = 0;
        let id = unsafe {
            CONTADOR += 1;
            CONTADOR
        };

        Self {
            nome: self.nome.clone(),
            max_conexoes: self.max_conexoes,
            conexoes_ativas: 0, // Reseta para zero no clone!
            id_interno: id,     // Gera novo ID unico
        }
    }
}

fn main() {
    // Cria um template de relatorio
    let template_relatorio = Documento {
        titulo: "Template de Relatorio Mensal".to_string(),
        metadados: Metadados {
            autor: "Sistema".to_string(),
            tags: vec!["relatorio".to_string(), "mensal".to_string()],
            propriedades: HashMap::from([
                ("formato".to_string(), "A4".to_string()),
                ("confidencial".to_string(), "sim".to_string()),
            ]),
        },
        blocos: vec![
            ConteudoBloco::Titulo("Relatorio Mensal - [MES/ANO]".to_string()),
            ConteudoBloco::Separador,
            ConteudoBloco::Paragrafo(
                "Este relatorio apresenta os resultados do periodo.".to_string(),
            ),
            ConteudoBloco::Tabela {
                cabecalho: vec![
                    "Metrica".to_string(),
                    "Valor".to_string(),
                    "Meta".to_string(),
                ],
                linhas: vec![], // sera preenchido
            },
            ConteudoBloco::Separador,
            ConteudoBloco::Paragrafo("Conclusao: [PREENCHER]".to_string()),
        ],
        criado_em: "2025-01-01".to_string(),
        versao: 5, // template na versao 5
    };

    // Cria documentos a partir do template
    let relatorio_jan = template_relatorio.criar_rascunho(
        "Relatorio Mensal - Janeiro/2025",
        "Maria Silva",
    );

    let relatorio_fev = template_relatorio.criar_rascunho(
        "Relatorio Mensal - Fevereiro/2025",
        "Joao Santos",
    );

    println!("Template: {} (v{})", template_relatorio.titulo, template_relatorio.versao);
    println!("Jan: {} (v{}) por {}", relatorio_jan.titulo, relatorio_jan.versao, relatorio_jan.metadados.autor);
    println!("Fev: {} (v{}) por {}", relatorio_fev.titulo, relatorio_fev.versao, relatorio_fev.metadados.autor);
    println!("Blocos no template: {}", template_relatorio.blocos.len());
    println!("Blocos em jan: {}", relatorio_jan.blocos.len());

    // Demonstra Clone customizado
    println!("\n=== Clone Customizado ===");
    let pool_original = ConexaoPool {
        nome: "pool-principal".to_string(),
        max_conexoes: 20,
        conexoes_ativas: 15, // 15 conexoes em uso
        id_interno: 1,
    };

    let pool_clone = pool_original.clone();
    println!(
        "Original: id={}, ativas={}",
        pool_original.id_interno, pool_original.conexoes_ativas
    );
    println!(
        "Clone: id={}, ativas={} (resetado!)",
        pool_clone.id_interno, pool_clone.conexoes_ativas
    );
}

Quando Usar

  • Criar variacoes de um template com pequenas modificacoes
  • Spawning de entidades em jogos (monstros, itens, NPCs)
  • Copia defensiva - quando voce precisa garantir que o chamador nao modifique seus dados
  • Configuracoes derivadas - criar nova config baseada em uma existente
  • Snapshots - salvar o estado atual de um objeto para restaurar depois

Quando NAO Usar

  • Tipos simples e pequenos - use Copy em vez de Clone
  • Grafos com ciclos - Clone nao lida bem com referencias circulares
  • Objetos muito grandes - clonar pode ser caro; considere Arc para compartilhamento
  • Quando compartilhamento basta - se nao precisa modificar, use &T ou Arc<T>
use std::sync::Arc;

// Se multiplas partes precisam COMPARTILHAR dados imutaveis,
// Arc e melhor que Clone:
let config = Arc::new(Config::carregar());
let config_clone = Arc::clone(&config); // barato: so incrementa contador

// Se cada parte precisa de sua PROPRIA copia para modificar,
// Clone e a escolha certa:
let mut minha_config = config_base.clone();
minha_config.porta = 9090;

Variacoes em Rust

1. Derive Clone (automatico)

// O compilador gera Clone automaticamente quando todos os campos sao Clone
#[derive(Clone)]
struct Simples {
    nome: String,
    valor: i32,
}

2. Clone customizado (logica especial)

impl Clone for MinhaStruct {
    fn clone(&self) -> Self {
        Self {
            dados: self.dados.clone(),
            cache: HashMap::new(), // limpa cache no clone
            id: gerar_novo_id(),   // novo ID para o clone
        }
    }
}

3. Prototype com trait object (polimorfismo)

pub trait Prototipavel: std::fmt::Debug {
    /// Cria uma copia do objeto como trait object
    fn clonar_prototipo(&self) -> Box<dyn Prototipavel>;
}

impl Clone for Box<dyn Prototipavel> {
    fn clone(&self) -> Self {
        self.clonar_prototipo()
    }
}

4. Cow (Copy-on-Write) como otimizacao

use std::borrow::Cow;

// Cow adia a copia ate que uma modificacao seja necessaria
fn processar(dados: Cow<str>) -> String {
    if dados.contains("erro") {
        // So clona se precisar modificar
        let mut corrigido = dados.into_owned();
        corrigido = corrigido.replace("erro", "corrigido");
        corrigido
    } else {
        // Sem copia - usa a referencia original
        dados.into_owned()
    }
}

Padroes Relacionados

  • Builder - Constroi objetos do zero; Prototype cria copias de um existente
  • Factory - Factory instancia tipos especificos; Prototype clona instancias
  • Composite - Arvores compostas frequentemente usam Clone para duplicar subarvores
  • Strategy - Prototipos de estrategias podem ser clonados para cada contexto

Conclusao

Em Rust, o padrao Prototype e tao natural que muitos desenvolvedores o usam sem perceber que estao aplicando um Design Pattern. A trait Clone fornece uma implementacao segura e idiomatica, enquanto Copy oferece uma versao otimizada para tipos simples. A possibilidade de implementar Clone manualmente permite controle fino sobre o que e copiado e o que e reinicializado, tornando o padrao extremamente flexivel. Combinado com o registro de prototipos, voce pode criar sistemas poderosos de templates que simplificam enormemente a criacao de objetos complexos.