Introdução: Dois mundos em uma linguagem
\n\nQuando você começa a estudar Rust, rapidamente percebe sua posição única. Esta linguagem não impõe uma única forma de pensar. Diferente de Java (onde tudo é objeto) ou Haskell (onde tudo é função), Rust oferece a liberdade de escolha. Mas com essa liberdade vem a responsabilidade: qual abordagem escolher — a funcional ou a orientada a objetos?
\n\nMuitos desenvolvedores que vêm para Rust do mundo Python ou C++ instintivamente se voltam para a POO (Programação Orientada a Objetos). Eles buscam classes, herança e padrões familiares. Outros, inspirados pelo mundo Haskell ou Scala, tentam programar em Rust exclusivamente no estilo funcional, usando mônadas, imutabilidade e composição.
\n\nA verdade, como sempre, está no meio. Rust é uma linguagem multiparadigma que permite aproveitar o melhor de ambos os mundos. Neste artigo, vamos explorar como usar efetivamente a abordagem funcional em Rust e quando aplicar padrões orientados a objetos. Você aprenderá a escrever código que não apenas funciona, mas também encanta pela sua elegância.
\n\nFundamentos de POO em Rust: Traits em vez de classes
\n\nVamos esclarecer desde já. Em Rust não existem classes no sentido clássico. Mas isso não significa que você não possa usar POO. Em vez de classes, usamos traits e structs. São eles que permitem implementar encapsulamento, polimorfismo e até mesmo uma certa semelhança com herança.
\n\nStructs e métodos
\n\nAs structs são seus "objetos". Você define os dados e, em seguida, com impl, adiciona métodos a eles:
struct Player {\n name: String,\n health: i32,\n level: u32,\n}\n\nimpl Player {\n // Método construtor\n fn new(name: &str) -> Self {\n Player {\n name: name.to_string(),\n health: 100,\n level: 1,\n }\n }\n\n // Método que altera o estado\n fn take_damage(&mut self, damage: i32) {\n self.health = (self.health - damage).max(0);\n }\n\n // Getter (método de acesso)\n fn is_alive(&self) -> bool {\n self.health > 0\n }\n}\n\n\nEste é o estilo POO puro: temos estado (campos da struct) e comportamento (métodos). Rust garante o encapsulamento através do controle de visibilidade (modificador pub).
Polimorfismo através de traits
\n\nEm vez de herança de classes, Rust usa polimorfismo através de traits. Isso é mais poderoso e seguro do que a herança clássica, pois você não enfrenta o "problema do diamante" ou a herança acidental de comportamentos indesejados.
\n\ntrait Attackable {\n fn attack(&self, target: &mut Player);\n}\n\nstruct Swordsman {\n damage: i32,\n}\n\nimpl Attackable for Swordsman {\n fn attack(&self, target: &mut Player) {\n target.take_damage(self.damage);\n println!("O espadachim causou {} de dano!", self.damage);\n }\n}\n\nstruct Mage {\n spell_power: i32,\n mana_cost: i32,\n}\n\nimpl Attackable for Mage {\n fn attack(&self, target: &mut Player) {\n target.take_damage(self.spell_power * 2);\n println!("O mago lançou uma bola de fogo com força {}!", self.spell_power * 2);\n }\n}\n\n\nAgora você pode trabalhar com qualquer objeto que implemente o trait Attackable, sem se preocupar com seu tipo específico:
fn perform_attack(attacker: &impl Attackable, target: &mut Player) {\n attacker.attack(target);\n}\n\n\nIsso é chamado de "duck typing" turbinado ou polimorfismo estático. Rust verifica tudo em tempo de compilação, portanto você não terá erros em tempo de execução.
\n\nAbordagem funcional: Iteradores e closures
\n\nAgora vamos mudar