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 tipo struct o enum correspondiente. Por ejemplo, x.clone() es un método del tipo de la variable x.
  • 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én Vec::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.