Personalizando a Saída de Structs Rust com fmt::Display

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Neste laboratório, você aprenderá como implementar o trait fmt::Display em Rust para personalizar a aparência da saída de uma estrutura. Você também explorará a diferença entre fmt::Display e fmt::Debug, bem como as limitações de fmt::Display para tipos de contêiner genéricos. Finalmente, você terá uma atividade para implementar o trait fmt::Display para uma nova estrutura Complex e imprimi-la em um formato específico.

Nota: Se o laboratório não especificar um nome de arquivo, você pode usar qualquer nome de arquivo que desejar. Por exemplo, você pode usar main.rs, compilar e executá-lo com rustc main.rs && ./main.

Display (Exibição)

fmt::Debug dificilmente parece compacto e limpo, por isso é frequentemente vantajoso personalizar a aparência da saída. Isso é feito implementando manualmente fmt::Display, que usa o marcador de impressão {}. Implementá-lo se parece com isto:

// Importe (via `use`) o módulo `fmt` para torná-lo disponível.
use std::fmt;

// Defina uma estrutura para a qual `fmt::Display` será implementado. Esta é
// uma estrutura de tupla chamada `Structure` que contém um `i32`.
struct Structure(i32);

// Para usar o marcador `{}`, o trait `fmt::Display` deve ser implementado
// manualmente para o tipo.
impl fmt::Display for Structure {
    // Este trait requer `fmt` com esta assinatura exata.
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Escreva estritamente o primeiro elemento no fluxo de saída fornecido:
        // `f`. Retorna `fmt::Result` que indica se a operação foi bem-sucedida
        // ou falhou. Observe que `write!` usa uma sintaxe muito semelhante a
        // `println!`.
        write!(f, "{}", self.0)
    }
}

fmt::Display pode ser mais limpo que fmt::Debug, mas isso apresenta um problema para a biblioteca std. Como os tipos ambíguos devem ser exibidos? Por exemplo, se a biblioteca std implementasse um único estilo para todos os Vec<T>, qual estilo deveria ser? Seria um destes dois?

  • Vec<path>: /:/etc:/home/username:/bin (dividido por :)
  • Vec<number>: 1,2,3 (dividido por ,)

Não, porque não existe um estilo ideal para todos os tipos e a biblioteca std não presume ditar um. fmt::Display não é implementado para Vec<T> ou para quaisquer outros contêineres genéricos. fmt::Debug deve então ser usado para esses casos genéricos.

Isso não é um problema, no entanto, porque para qualquer novo tipo de contêiner que não seja genérico, fmt::Display pode ser implementado.

use std::fmt; // Importa `fmt`

// Uma estrutura que contém dois números. `Debug` será derivado para que os resultados
// possam ser contrastados com `Display`.
#[derive(Debug)]
struct MinMax(i64, i64);

// Implementa `Display` para `MinMax`.
impl fmt::Display for MinMax {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Use `self.number` para se referir a cada ponto de dados posicional.
        write!(f, "({}, {})", self.0, self.1)
    }
}

// Define uma estrutura onde os campos são nomeáveis para comparação.
#[derive(Debug)]
struct Point2D {
    x: f64,
    y: f64,
}

// Da mesma forma, implementa `Display` para `Point2D`.
impl fmt::Display for Point2D {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Personaliza para que apenas `x` e `y` sejam denotados.
        write!(f, "x: {}, y: {}", self.x, self.y)
    }
}

fn main() {
    let minmax = MinMax(0, 14);

    println!("Compare structures:");
    println!("Display: {}", minmax);
    println!("Debug: {:?}", minmax);

    let big_range =   MinMax(-300, 300);
    let small_range = MinMax(-3, 3);

    println!("The big range is {big} and the small is {small}",
             small = small_range,
             big = big_range);

    let point = Point2D { x: 3.3, y: 7.2 };

    println!("Compare points:");
    println!("Display: {}", point);
    println!("Debug: {:?}", point);

    // Error. Both `Debug` and `Display` were implemented, but `{:b}`
    // requires `fmt::Binary` to be implemented. This will not work.
    // println!("What does Point2D look like in binary: {:b}?", point);
}

Portanto, fmt::Display foi implementado, mas fmt::Binary não foi, e, portanto, não pode ser usado. std::fmt tem muitos desses traits e cada um requer sua própria implementação. Isso é detalhado mais em std::fmt.

Atividade

Após verificar a saída do exemplo acima, use a estrutura Point2D como guia para adicionar uma estrutura Complex ao exemplo. Quando impresso da mesma forma, a saída deve ser:

Display: 3.3 + 7.2i
Debug: Complex { real: 3.3, imag: 7.2 }

Resumo

Parabéns! Você concluiu o laboratório Display. Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.