Display
fmt::Debug
редко выглядит компактным и чистым, поэтому часто выгодно настроить внешний вид вывода. Это делается путём ручного реализации fmt::Display
, который использует маркер вывода {}
. Реализация выглядит так:
// Импортируем (через `use`) модуль `fmt`, чтобы иметь возможность использовать его.
use std::fmt;
// Определяем структуру, для которой будет реализован `fmt::Display`. Это
// кортежная структура под названием `Structure`, которая содержит `i32`.
struct Structure(i32);
// Чтобы использовать маркер `{}`, для типа необходимо вручную реализовать
// трейт `fmt::Display`.
impl fmt::Display for Structure {
// Этот трейт требует `fmt` с такой точной сигнатурой.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Точно записываем первый элемент в предоставляемый поток вывода: `f`.
// Возвращает `fmt::Result`, который показывает, удалось ли выполнить
// операцию или нет. Обратите внимание, что `write!` использует синтаксис,
// очень похожий на `println!`.
write!(f, "{}", self.0)
}
}
fmt::Display
может быть чище, чем fmt::Debug
, но это представляет проблему для стандартной библиотеки std
. Как следует выводить неоднозначные типы? Например, если стандартная библиотека реализовала единый стиль для всех Vec<T>
, какой стиль должен быть выбран? Будет ли это один из двух вариантов?
Vec<путь>
: /:/etc:/home/username:/bin
(разделено по :
)
Vec<число>
: 1,2,3
(разделено по ,
)
Нет, потому что для всех типов нет идеального стиля, и стандартная библиотека не настаивает на одном стиле. fmt::Display
не реализован для Vec<T>
или для любых других обобщенных контейнеров. Для таких обобщенных случаев необходимо использовать fmt::Debug
.
Однако это не проблема, потому что для любого нового контейнерного типа, который не является обобщенным, можно реализовать fmt::Display
.
use std::fmt; // Импортируем `fmt`
// Структура, которая хранит два числа. Будет получен `Debug` для того, чтобы
// результаты можно было сравнить с `Display`.
#[derive(Debug)]
struct MinMax(i64, i64);
// Реализуем `Display` для `MinMax`.
impl fmt::Display for MinMax {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Используем `self.number`, чтобы сослаться на каждую позиционную
// точку данных.
write!(f, "({}, {})", self.0, self.1)
}
}
// Определяем структуру, где поля имеют имена для сравнения.
#[derive(Debug)]
struct Point2D {
x: f64,
y: f64,
}
// Аналогично реализуем `Display` для `Point2D`.
impl fmt::Display for Point2D {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Настраиваем вывод так, чтобы обозначались только `x` и `y`.
write!(f, "x: {}, y: {}", self.x, self.y)
}
}
fn main() {
let minmax = MinMax(0, 14);
println!("Сравним структуры:");
println!("Display: {}", minmax);
println!("Debug: {:?}", minmax);
let big_range = MinMax(-300, 300);
let small_range = MinMax(-3, 3);
println!("Большой диапазон: {big}, а маленький: {small}",
small = small_range,
big = big_range);
let point = Point2D { x: 3.3, y: 7.2 };
println!("Сравним точки:");
println!("Display: {}", point);
println!("Debug: {:?}", point);
// Ошибка. И `Debug`, и `Display` были реализованы, но `{:b}` требует
// реализации `fmt::Binary`. Это не сработает.
// println!("Как выглядит Point2D в двоичном виде: {:b}?", point);
}
Таким образом, fmt::Display
был реализован, но fmt::Binary
не был, и поэтому его нельзя использовать. В std::fmt
есть много таких трейтов
, и каждый требует собственной реализации. Подробнее об этом описано в std::fmt
.
Практическое задание
После проверки вывода примера выше, используйте структуру Point2D
в качестве руководства для добавления структуры Complex
в пример. При выводе аналогично предыдущему примеру, вывод должен быть таким:
Display: 3.3 + 7.2i
Debug: Complex { real: 3.3, imag: 7.2 }