En Rust, la forma en que representamos los datos depende de quién sea el receptor de la información: un desarrollador o un usuario final. Para gestionar esto, el lenguaje utiliza dos traits principales: Debug y Display.

El trait Debug está diseñado para la depuración. Permite inspeccionar rápidamente la estructura interna de un tipo de dato. Gracias a la macro #[derive(Debug)], Rust puede generar automáticamente una implementación que muestra el nombre de la estructura y sus campos. Se utiliza con el marcador de posición {:?} para una salida estándar o {:#?} para una salida “bonita” (pretty-print) con saltos de línea y sangrías.

Por otro lado, el trait Display es para la representación de usuario. A diferencia de Debug, no se puede derivar automáticamente; requiere una implementación manual del método fmt. Esto es así porque la interfaz de usuario debe ser limpia y legible, no necesariamente una representación técnica de la estructura de datos. Se utiliza con el marcador {}. Si se intenta usar {} con un tipo que no implementa Display, el compilador emitirá un error.

Además de estos traits, Rust ofrece macros potentes para la salida de datos: print! y println! envían texto a la salida estándar (stdout), mientras que eprint! y eprintln! lo envían a la salida de error (stderr). Si en lugar de imprimir necesitas guardar el texto formateado en una variable, utilizas la macro format!, que retorna una String.

El sistema de formateo también permite un control preciso sobre la apariencia de los datos mediante modificadores:
* Alineación: {:>10} alinea a la derecha con 10 espacios; {:<10} alinea a la izquierda.
* Relleno: {:0>5} rellena con ceros a la izquierda para alcanzar 5 caracteres.
* Precisión: {:.2} limita la salida a dos decimales.
* Bases: {:x} para hexadecimal y {:b} para binario.

Desde Rust 1.58, puedes capturar variables directamente en el format string usando {variable} en lugar de pasarla como argumento separado.

use std::fmt;

// Implementación automática para desarrolladores
#[derive(Debug)]
struct Punto {
    x: f64,
    y: f64,
}

// Implementación manual para usuarios finales
impl fmt::Display for Punto {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // write! escribe directamente en el buffer del formateador
        write!(f, "({}, {})", self.x, self.y)
    }
}

fn main() {
    let punto = Punto { x: 3.14159, y: 2.0 };
    let id_usuario = 42;
    let nombre = "Rustacean";

    // 1. Uso de Debug ({:?} y {:#?})
    println!("Debug (estándar): {:?}", punto);
    println!("Debug (bonito):\n{:#?}", punto);

    // 2. Uso de Display ({})
    println!("Display (usuario): {}", punto);

    // 3. Formateo avanzado de números
    let numero: u32 = 255;
    println!("Decimal: {}", numero);
    println!("Hexadecimal: {:x}", numero);
    println!("Binario: {:b}", numero);
    println!("Precisión decimal: {:.2}", punto.x);
    println!("Alineación y relleno: {:0>5}", 7);

    // 4. Captura directa de variables (Rust 1.58+)
    println!("Hola, {nombre}! Tu ID es {id_usuario}");

    // 5. Uso de format! para obtener un String
    let mensaje_personalizado = format!("Coordenadas: {}", punto);
    println!("String guardado: {}", mensaje_personalizado);
}

Explicación del Código

  • #[derive(Debug)]: Esta instrucción le dice al compilador que genere automáticamente la implementación del trait Debug para la estructura Punto.
  • impl fmt::Display for Punto: Define cómo se mostrará Punto cuando usemos {}.
  • fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result: Es la firma obligatoria del método para Display. Recibe una referencia a sí mismo (&self) y un formateador (f).
  • write!(f, "({}, {})", self.x, self.y): Dentro de fmt, se usa write! para enviar el formato deseado al buffer f. Accedemos a los campos self.x y self.y de la instancia.
  • println!("{:?}", punto): Utiliza la implementación de Debug para imprimir la estructura de forma técnica.
  • println!("{:#?}", punto): Utiliza la versión expandida de Debug para una visualización jerárquica.
  • println!("{}", punto): Llama a nuestra implementación personalizada de Display.
  • {:x} y {:b}: Formatea el entero numero en hexadecimal y binario, respectivamente.
  • {id_usuario}: Utiliza la funcionalidad de captura directa de variables introducida en Rust 1.58 para insertar id_usuario sin usar la coma.
  • format!(...): A diferencia de println!, la macro format! no imprime en la consola, sino que construye una nueva String con el contenido formateado.

47