Definición del comportamiento compartido con rasgos

Completado

Un rasgo es una interfaz común que puede implementar un grupo de tipos. La biblioteca estándar de Rust tiene muchos rasgos útiles tales como:

  • io::Read para los valores que pueden leer bytes de un origen.
  • io::Write para los valores que pueden escribir bytes.
  • Debug para los valores que se pueden imprimir en la consola mediante el especificador de formato "{:?}".
  • Clone para los valores que se pueden duplicar explícitamente en la memoria.
  • ToString para los valores que se pueden convertir en un objeto String.
  • Default para los tipos que tienen un valor predeterminado razonable, como cero para los números, vacío para los vectores y "" para String.
  • Iterator para los tipos que pueden generar una secuencia de valores.

Cada definición de rasgo es una colección de métodos definidos para un tipo desconocido, que normalmente representa una capacidad o un comportamiento que pueden realizar sus implementadores.

Para representar el concepto de "tener un área bidimensional", podemos definir el siguiente rasgo:

trait Area {
    fn area(&self) -> f64;
}

Aquí, se declara un rasgo mediante la palabra clave trait y luego el nombre del rasgo, que en este caso es Area.

Dentro de las llaves, se declaran las signaturas de método que describen los comportamientos de los tipos que implementan este rasgo, que en este caso es la firma de la función fn area(&self) -> f64. El compilador comprobará entonces que cada tipo que implementa este rasgo debe proporcionar su propio comportamiento personalizado para el cuerpo del método.

Ahora, vamos a crear algunos nuevos tipos que implementarán nuestro rasgo Area:

struct Circle {
    radius: f64,
}

struct Rectangle {
    width: f64,
    height: f64,
}

impl Area for Circle {
    fn area(&self) -> f64 {
        use std::f64::consts::PI;
        PI * self.radius.powf(2.0)
    }
}

impl Area for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

Para implementar un rasgo para un tipo, usamos las palabras clave impl Trait for Type, donde Trait es el nombre del rasgo que se implementa y Type es el nombre de la estructura del implementador o la enumeración.

Dentro del bloque impl, se colocan las signaturas de método que la definición de rasgo requiere y se rellena el cuerpo del método con el comportamiento específico que se quiere que tengan los métodos del rasgo para el tipo determinado.

Cuando un tipo implementa un rasgo determinado, promete mantener su contrato. Después de implementar el rasgo, podemos llamar a los métodos en instancias de Circle y Rectangle de la misma forma que llamamos a métodos normales de la siguiente manera:

let circle = Circle { radius: 5.0 };
let rectangle = Rectangle {
    width: 10.0,
    height: 20.0,
};

println!("Circle area: {}", circle.area());
println!("Rectangle area: {}", rectangle.area());

Se puede interactuar con este código en este vínculo al área de juegos de Rust.