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
Copyem vez deClone - Grafos com ciclos -
Clonenao lida bem com referencias circulares - Objetos muito grandes - clonar pode ser caro; considere
Arcpara compartilhamento - Quando compartilhamento basta - se nao precisa modificar, use
&TouArc<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.