Introducción: Dos mundos en un mismo lenguaje
\n\nCuando empiezas a aprender Rust, rápidamente notas su posición única. Este lenguaje no te impone una única forma de pensar. A diferencia de Java (donde todo es un objeto) o Haskell (donde todo es una función), Rust te ofrece libertad de elección. Pero con esa libertad viene la responsabilidad: ¿qué enfoque elegir: el funcional o el orientado a objetos?
\n\nMuchos desarrolladores que llegan a Rust desde el mundo de Python o C++ se sienten atraídos intuitivamente por la POO (programación orientada a objetos). Buscan clases, herencia y patrones familiares. Otros, inspirados por el mundo de Haskell o Scala, intentan escribir en Rust exclusivamente en estilo funcional, usando mónadas, inmutabilidad y composición.
\n\nLa verdad, como siempre, está en el medio. Rust es un lenguaje multiparadigma que te permite tomar lo mejor de ambos mundos. En este artículo, analizaremos cómo usar eficazmente el enfoque funcional en Rust y cuándo aplicar patrones orientados a objetos. Aprenderás a escribir código que no solo funcione, sino que también deleite por su elegancia.
\n\nFundamentos de POO en Rust: Traits en lugar de clases
\n\nAclaremos las cosas de inmediato. En Rust no hay clases en el sentido clásico. Pero eso no significa que no puedas usar POO. En lugar de clases, aquí se utilizan traits y structs. Son ellos los que permiten implementar encapsulamiento, polimorfismo e incluso cierto parecido a la herencia.
\n\nStructs y métodos
\n\nLas structs son tus «objetos». Defines los datos y luego, con impl, les agregas métodos:
struct Player {\n name: String,\n health: i32,\n level: u32,\n}\n\nimpl Player {\n // Método constructor\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 cambia el estado\n fn take_damage(&mut self, damage: i32) {\n self.health = (self.health - damage).max(0);\n }\n\n // Getter (método de acceso)\n fn is_alive(&self) -> bool {\n self.health > 0\n }\n}\n\n\nEsto es puro estilo POO: tenemos estado (campos de la struct) y comportamiento (métodos). Rust proporciona encapsulamiento mediante el control de visibilidad (modificador pub).
Polimorfismo a través de traits
\n\nEn lugar de la herencia de clases, en Rust se usa el polimorfismo a través de traits. Esto es más potente y seguro que la herencia clásica, ya que no te encuentras con el «problema del diamante» ni con la herencia accidental de comportamientos no deseados.
\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!("¡El espadachín infligió {} de daño!", 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!("¡El mago lanzó una bola de fuego de potencia {}!", self.spell_power * 2);\n }\n}\n\n\nAhora puedes trabajar con cualquier objeto que implemente el trait Attackable, sin preocuparte por su tipo concreto:
fn perform_attack(attacker: &impl Attackable, target: &mut Player) {\n attacker.attack(target);\n}\n\n\nEsto se llama «tipado pato con esteroides» o polimorfismo estático. Rust verifica todo en tiempo de compilación, por lo que no obtendrás un error en tiempo de ejecución.
\n\nEnfoque funcional: Iteradores y clausuras
\n\nAhora cambiemos