Implementando funciones para structs y enums
Los struct
y enum
permiten que se puedan definir funciones asociadas a los tipos definidos como tal. Esto da mucha capacidad al lenguaje. Para ello, se utiliza el bloque impl
sobre el tipo de datos definido con struct
o enum
. A estas funciones se las llama métodos.
En un bloque impl
se pueden definir dos tipos diferentes de métodos:
- Métodos: que toman como primer parámetro uno denominado
self
(o&self
o&mut self
). Estos métodos, para utilizarlos, usan.
(un punto) sobre una variable del tipostruct
oenum
correspondiente. Por ejemplo,x.clone()
es un método del tipo de la variablex
. - Funciones asociadas (al tipo). Que en otros lenguajes se conocen como métodos estáticos: No tienen el primer parámetros
self
. Son funciones "relacionadas con el tipo de datos". Se llaman utilizando::
. Por ejemplo:String::from()
es una llamada a una función asociada. TambiénVec::new()
. Normalmente se utilizan para crear valores de variables del tipo correspondiente.
El ejemplo que se presenta a continuación, crea animales y los imprime.
En el siguiente ejemplo, también conviene observar que para poder usar {:?}
al imprimir un tipo, este debe tener el rasgo de ser depurable, lo que se consigue mediante #derive(Debug)
colocado al inicio del tipo de datos. A este tipo de etiquetado con #
seguido de un nombre, se le denomina en Rust atributo. Se utilizan para indicar acciones al compilador. En este caso, para que se implemente de forma automática la posibilidad de depuración al tipo de datos correspondiente. Existen muchos atributos diferentes que se pueden utilizar en un programa Rust, más adelante se verán otros. El más común es derive
y se encuentra muchas veces precediendo la definición de un struct
o enum
.
#[derive(Debug)] struct Animal { edad: u8, tipo_animal: TipoAnimal, } #[derive(Debug)] enum TipoAnimal { Gato, Perro, } impl Animal { fn new() -> Self { // Self, aquí, significa Animal. // También se podría haber usado Animal // en lugar de Self Self { // Cuando se escriba Animal::new(), se obtendrá siempre un gato de 10 años edad: 10, tipo_animal: TipoAnimal::Gato, } } fn cambiar_a_perro(&mut self) { // como está dentro de Animal, &mut self significa &mut Animal // usa .cambiar_a_perro() para convertir el cato en un perro // con &mut self se puede modificar println!("¡Cambiando el animal a perro!"); self.tipo_animal = TipoAnimal::Perro; } fn cambiar_a_gato(&mut self) { // usa .cambiar_a_gato() para cambiar el perro a gato // con &mut self se puede modificar println!("¡Cambiando el animal a gato!"); self.tipo_animal = TipoAnimal::Gato; } fn comprobar_tipo(&self) { // se lee a sí mismo self match self.tipo_animal { TipoAnimal::Perro => println!("El animal es un perro"), TipoAnimal::Gato => println!("El animal es un gato"), } } } fn main() { let mut animal_nuevo = Animal::new(); // Función asociada para crear una variable Animal // Es un gato de 10 años animal_nuevo.comprobar_tipo(); animal_nuevo.cambiar_a_perro(); animal_nuevo.comprobar_tipo(); animal_nuevo.cambiar_a_gato(); animal_nuevo.comprobar_tipo(); }
Esto imprime:
El animal es un gato
¡Cambiando el animal a perro!
El animal es un perro
¡Cambiando el animal a gato!
El animal es un gato
Se debe recordar que Self
(el tipo Self) y self
(la variable self) funcionan como abreviaturas del tipo que sea en cada momento.
En el código anterior, Self
es igual a Animal
. Y en fn cambiar_a_perro(&mut self)
significa que el parámetro primero es un Animal
. Este parámetro es la variable animal_nuevo
cuando se llama de la siguiente forma animal_nuevo.cambiar_a_perro()
.
A continuación se muestra un ejemplo más de impl
. En este caso, con enum
.
enum Estado { Bueno, Malo, Somnoliento, } impl Estado { fn consultar(&self) { match self { Estado::Bueno => println!("¡Me siento bien!"), Estado::Malo => println!("Eh, no me siento tan bien"), Estado::Somnoliento => println!("Necesito dormir AHORA"), } } } fn main() { let mi_estado = Estado::Somnoliento; mi_estado.consultar(); }
This prints Necesito dormir AHORA
.