Трейт Display в Rust

Сводная таблица встроенных типов с реализацией Display

ТипПример выводаПримечания
i8, i16, i32, i64, i128, isize-42, 123Целочисленные со знаком
u8, u16, u32, u64, u128, usize42, 255Целочисленные без знака
f32, f643.14, -0.001Числа с плавающей точкой
booltrue, falseЛогические значения
char'a', '🦀'Символы Unicode
&str, String"hello", "world"Строки
Option<T>Some(42), NoneЕсли T реализует Display
Result<T, E>Ok(42), Err("error")Если T и E реализуют Display
()()Пустой кортеж
&TТо же, что TСсылки
Box<T>То же, что TУмный указатель
Rc<T>, Arc<T>То же, что TСчетчики ссылок
Vec<T>[1, 2, 3]Если T реализует Display
[T; N][1, 2, 3]Массивы
Cell<T>, RefCell<T>То же, что TВнутренняя изменяемость
Mutex<T>, RwLock<T>То же, что TСинхронизация
Path, PathBuf/home/user/file.txtПути файловой системы
IpAddr, SocketAddr192.168.1.1:8080Сетевые адреса
Duration2.5s, 500msПромежутки времени
SystemTime, InstantВременные меткиВремя системы

Способы реализации Display для пользовательских типов

1. Ручная реализация через fmt

use std::fmt;

struct Point {
    x: i32,
    y: i32,
}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

fn main() {
    let p = Point { x: 10, y: 20 };
    println!("Точка: {}", p); // Точка: (10, 20)
}

2. Использование макроса write! для сложных форматов

use std::fmt;

struct Color {
    red: u8,
    green: u8,
    blue: u8,
}

impl fmt::Display for Color {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "RGB({}, {}, {}) 0x{:02X}{:02X}{:02X}", 
               self.red, self.green, self.blue,
               self.red, self.green, self.blue)
    }
}

fn main() {
    let color = Color { red: 255, green: 128, blue: 0 };
    println!("{}", color); // RGB(255, 128, 0) 0xFF8000
}

3. Для перечислений (enum)

use std::fmt;

enum HttpStatus {
    Ok,
    NotFound,
    InternalServerError,
}

impl fmt::Display for HttpStatus {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            HttpStatus::Ok => write!(f, "200 OK"),
            HttpStatus::NotFound => write!(f, "404 Not Found"),
            HttpStatus::InternalServerError => write!(f, "500 Internal Server Error"),
        }
    }
}

fn main() {
    let status = HttpStatus::NotFound;
    println!("Статус: {}", status); // Статус: 404 Not Found
}

4. Для обобщенных типов

use std::fmt;

struct Pair<T> {
    first: T,
    second: T,
}

impl<T: fmt::Display> fmt::Display for Pair<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.first, self.second)
    }
}

fn main() {
    let int_pair = Pair { first: 1, second: 2 };
    let str_pair = Pair { first: "hello", second: "world" };
    
    println!("{}", int_pair); // (1, 2)
    println!("{}", str_pair); // (hello, world)
}

5. Использование derive макроса (через сторонние крейты)

// В Cargo.toml: 
// [dependencies]
// derive_more = "1.0"

use derive_more::Display;

#[derive(Display)]
#[display(fmt = "Пользователь: {} ({} лет)", name, age)]
struct User {
    name: String,
    age: u32,
}

fn main() {
    let user = User { name: "Анна".to_string(), age: 25 };
    println!("{}", user); // Пользователь: Анна (25 лет)
}

6. С обработкой форматирования

use std::fmt;

struct Temperature {
    celsius: f64,
}

impl fmt::Display for Temperature {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let precision = f.precision().unwrap_or(1);
        write!(f, "{:.precision$}°C", self.celsius, precision = precision)
    }
}

fn main() {
    let temp = Temperature { celsius: 23.456 };
    
    println!("{}", temp);        // 23.5°C
    println!("{:.2}", temp);     // 23.46°C
    println!("{:.0}", temp);     // 23°C
}

7. Для типов с владением и заимствованием

use std::fmt;

// Для owned типа
struct Person {
    name: String,
    age: u8,
}

impl fmt::Display for Person {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} - {} лет", self.name, self.age)
    }
}

// Для типа с ссылками
struct Name<'a> {
    first: &'a str,
    last: &'a str,
}

impl<'a> fmt::Display for Name<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} {}", self.first, self.last)
    }
}

fn main() {
    let person = Person { name: "Иван".to_string(), age: 30 };
    let name = Name { first: "Мария", last: "Петрова" };
    
    println!("{}", person); // Иван - 30 лет
    println!("{}", name);   // Мария Петрова
}

Практический пример с комплексной структурой

use std::fmt;

struct BlogPost {
    title: String,
    author: String,
    content: String,
    likes: u32,
}

impl fmt::Display for BlogPost {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        writeln!(f, "📝 {}", self.title)?;
        writeln!(f, "✍️ Автор: {}", self.author)?;
        writeln!(f)?;
        
        // Обрезаем контент для краткости
        let preview = if self.content.len() > 100 {
            &self.content[..100]
        } else {
            &self.content
        };
        
        write!(f, "{}{}", preview, if self.content.len() > 100 { "..." } else { "" })?;
        writeln!(f)?;
        write!(f, "❤️ {} лайков", self.likes)
    }
}

fn main() {
    let post = BlogPost {
        title: "Мой первый пост в Rust".to_string(),
        author: "Алексей".to_string(),
        content: "Сегодня я изучал трейт Display в Rust. Это очень полезный трейт для форматирования вывода...".to_string(),
        likes: 42,
    };
    
    println!("{}", post);
}

Вывод:

📝 Мой первый пост в Rust
✍️ Автор: Алексей

Сегодня я изучал трейт Display в Rust. Это очень полезный трейт для форматирования вывода...
❤️ 42 лайков

Ключевые особенности реализации Display:

  1. Требуется только один метод - fmt
  2. Возвращает fmt::Result - специализированный Result для форматирования
  3. Использует Formatter для гибкого форматирования
  4. Может использовать все возможности форматирования (ширина, точность, выравнивание)
  5. Должен быть всегда успешным (в отличие от Debug, который может паниковать)

fmt::Result

Особенности:

#![allow(unused)]
fn main() {
pub type Result = result::Result<(), Error>;
}
ХарактеристикаОписание
ТипПсевдоним для Result<(), std::fmt::Error>
УспехOk(()) - форматирование выполнено
ОшибкаErr(Error) - ошибка форматирования
Error типstd::fmt::Error - нумератор без вариантов
ИспользованиеВсегда через ? оператор

Примеры:

#![allow(unused)]
fn main() {
use std::fmt;

impl fmt::Display for MyType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // При ошибке автоматически возвращается Err(Error)
        write!(f, "Значение: {}", self.value)?;
        Ok(()) // Явный возврат успеха
    }
}
}

Formatter

Основные возможности:

МетодНазначениеПример
write_str(&mut self, s: &str)Запись строкиf.write_str("hello")?
write_fmt(&mut self, args: Arguments)Запись форматированияf.write_fmt(format_args!("{}", x))?

Методы для получения параметров форматирования:

МетодВозвращаетОписание
width()Option<usize>Запрошенная ширина поля
precision()Option<usize>Запрошенная точность
align()Option<Alignment>Выравнивание
fill()charСимвол-заполнитель
sign_plus()boolПоказывать ли + для положительных
sign_minus()boolВсегда true
alternate()boolАльтернативный формат #
sign_aware_zero_pad()boolЗаполнение нулями с учетом знака

Alignment (выравнивание):

#![allow(unused)]
fn main() {
pub enum Alignment {
    Left,    // <
    Center,  // ^  
    Right,   // >
}
}

Практические примеры

1. Простая реализация с обработкой ошибок

#![allow(unused)]
fn main() {
use std::fmt;

struct SafeDisplay(String);

impl fmt::Display for SafeDisplay {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Каждый write! возвращает fmt::Result
        write!(f, "Safe: ")?;      // Если ошибка - сразу возврат
        write!(f, "{}", self.0)?;  // Если ошибка - сразу возврат
        Ok(()) // Все успешно
    }
}
}

2. Использование параметров форматирования

use std::fmt;

struct FlexibleNumber(i32);

impl fmt::Display for FlexibleNumber {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match (f.width(), f.precision()) {
            (Some(w), Some(p)) => write!(f, "{:0>width$.prec$}", self.0, width = w, prec = p),
            (Some(w), None) => write!(f, "{:>width$}", self.0, width = w),
            (None, Some(p)) => write!(f, "{:.prec$}", self.0, prec = p),
            (None, None) => write!(f, "{}", self.0),
        }
    }
}

fn main() {
    let num = FlexibleNumber(42);
    println!("{}", num);        // 42
    println!("{:5}", num);      //    42
    println!("{:05}", num);     // 00042
    println!("{:.2}", num);     // 42.00
}

3. Обработка выравнивания и заполнения

#![allow(unused)]
fn main() {
use std::fmt;

struct DecoratedText(&'static str);

impl fmt::Display for DecoratedText {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let width = f.width().unwrap_or(10);
        let fill = f.fill();
        
        match f.align() {
            Some(fmt::Alignment::Left) => {
                write!(f, "{:<width$}", self.0, width = width)
            },
            Some(fmt::Alignment::Center) => {
                write!(f, "{:^width$}", self.0, width = width)
            },
            Some(fmt::Alignment::Right) => {
                write!(f, "{:>width$}", self.0, width = width)
            },
            None => write!(f, "{}", self.0),
        }
    }
}
}

4. Комплексный пример с форматированием

use std::fmt;

struct SmartFloat(f64);

impl fmt::Display for SmartFloat {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let precision = f.precision().unwrap_or(2);
        
        if f.alternate() {
            // Альтернативный формат с #
            write!(f, "≈{:.prec$}", self.0, prec = precision)
        } else if f.sign_plus() {
            // Всегда показывать знак
            write!(f, "{:+.prec$}", self.0, prec = precision)
        } else {
            // Стандартный формат
            write!(f, "{:.prec$}", self.0, prec = precision)
        }
    }
}

fn main() {
    let num = SmartFloat(3.14159);
    println!("{}", num);      // 3.14
    println!("{:#}", num);    // ≈3.14
    println!("{:+}", num);    // +3.14
    println!("{:.4}", num);   // 3.1416
}

Ключевые особенности

Formatter:

  • "Писатель" с информацией о форматировании
  • Небуферизованный - пишет напрямую в выходной поток
  • Предоставляет контекст форматирования (ширина, точность, выравнивание)
  • Безопасный - гарантирует корректное UTF-8

fmt::Result:

  • Специализированный Result только для форматирования
  • Ошибка всегда одна - fmt::Error (пустой enum)
  • Идиоматическое использование - распространение через ?
  • Не требует обработки ошибок - обычно просто пробрасывается

Идиоматический шаблон:

#![allow(unused)]
fn main() {
impl fmt::Display for MyType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.field1)?;  // ? для распространения ошибок
        write!(f, " - ")?;
        write!(f, "{}", self.field2)?;
        Ok(()) // Явный возврат успеха в конце
    }
}
}