Compartir comportamiento con traits

RustBeginner
Practicar Ahora

Introducción

A veces, tipos de datos no relacionados necesitan garantizar el mismo comportamiento. Un sensor de invernadero y un envío tienen campos diferentes, pero ambos pueden describir su estado actual. Los traits de Rust permiten nombrar esa capacidad compartida sin obligar a los tipos a tener la misma estructura de datos.

En este laboratorio, definirá un trait pequeño, lo implementará para dos estructuras preparadas y pasará ambos valores a una función genérica de generación de informes. Las estructuras más largas y los datos de ejemplo ya están creados, para que cada modificación se mantenga centrada en el objetivo.

Definir un contrato de estado compartido

En este paso, definirá un trait que nombre un comportamiento compartido por tipos que, por lo demás, no están relacionados.

Acceda al paquete preparado e inspeccione el esqueleto completo. El comando sed -n muestra las líneas solicitadas sin modificarlas:

cd /home/labex/project/status-reporter
sed -n '1,160p' src/main.rs

Sensor y Shipment tienen campos diferentes. Sus campos de texto preparados utilizan &'static str, es decir, literales de cadena prestados que siguen siendo válidos durante todo el programa. La anotación de tiempo de vida 'static no forma parte de esta lección sobre traits y se tratará más adelante de forma intencionada; aquí no necesita modificarla ni analizarla.

Debajo de los marcadores de posición, show_status ya es genérica. Lea T: Status en tres partes: T representa un tipo concreto, los dos puntos significan «debe cumplir» y Status es el trait requerido. En lenguaje sencillo: «para cualquier tipo T, siempre que T implemente Status». Esta garantía permite que la función llame a item.status() de forma segura.

El trait debe existir antes de que Rust pueda comprender el límite. Abra el código fuente:

nano src/main.rs

Sustituya // STATUS_TRAIT por:

trait Status {
    fn status(&self) -> String;
}

La línea del método termina en punto y coma porque el trait declara un método obligatorio, pero no elige ninguna implementación. &self permite que el método inspeccione un valor sin adquirir su propiedad, y String permite que cada tipo construya su propio texto de estado. Cada implementación posterior debe repetir exactamente este nombre de método, receptor y tipo de retorno, y después proporcionar su propio cuerpo.

Guarde con Ctrl+O, pulse Enter y salga con Ctrl+X. Después, compruebe el paquete:

cargo check

Es posible que vea advertencias que indican que el trait y los elementos preparados todavía no se utilizan. La línea final Finished es la evidencia importante: el contrato compartido y el límite genérico son válidos. En los dos pasos siguientes añadirá las implementaciones y las llamadas.

Implementar Status para Sensor

En este paso, cumplirá el contrato del trait para Sensor y enviará un sensor mediante el generador de informes compartido.

Un bloque impl Trait for Type conecta un trait existente con un tipo concreto. La firma de su método debe coincidir con la del trait, pero el cuerpo puede utilizar los campos propios de ese tipo.

Abra el código fuente:

nano src/main.rs

Sustituya // SENSOR_IMPLEMENTATION por esta implementación breve:

impl Status for Sensor {
    fn status(&self) -> String {
        format!("{}: {}°C", self.location, self.celsius)
    }
}

format! construye y devuelve un String; por sí solo no muestra nada. La implementación puede utilizar location y celsius porque self es un Sensor prestado.

A continuación, dentro de main, sustituya // SENSOR_REPORT por:

    show_status("Sensor", &sensor);

El argumento &sensor coincide con el parámetro prestado &T del generador de informes, por lo que sensor sigue siendo propiedad de main. Guarde el archivo y salga de Nano. Después, ejecute el programa:

cargo run --quiet
Sensor: Greenhouse: 24°C

Esta línea demuestra que Sensor cumple Status y puede pasar por el generador de informes genérico con límite. El envío todavía no se ha conectado.

Implementar Status para Shipment

En este paso, implementará el mismo trait para Shipment y comprobará que un único generador de informes acepta ambos tipos concretos.

Vuelva a abrir el código fuente:

nano src/main.rs

Sustituya // SHIPMENT_IMPLEMENTATION por:

impl Status for Shipment {
    fn status(&self) -> String {
        format!("#{} {}", self.id, self.stage)
    }
}

Este método sigue el mismo contrato del trait, pero utiliza los campos propios del envío: id y stage. A continuación, sustituya // SHIPMENT_REPORT por:

    show_status("Shipment", &shipment);

Guarde el archivo y salga de Nano. Ejecute el paquete completo:

cargo run --quiet
Sensor: Greenhouse: 24°C
Shipment: #204 in transit

Ambas líneas proceden de show_status<T: Status>. El límite describe el comportamiento que necesita la función, mientras que cada implementación controla cómo su propio valor proporciona ese comportamiento.

Resumen

Definió un comportamiento compartido mediante un trait, lo implementó de forma independiente para dos estructuras y utilizó un límite de trait básico para que un único generador de informes genérico aceptara ambos tipos.