В Rust форматирование вывода осуществляется с помощью макросов, таких как println!, format!, write! и других, которые используют спецификаторы формата внутри фигурных скобок {}. Эти спецификаторы определяют, как данные будут представлены в текстовом виде, и зависят от трейтов форматирования из модуля std::fmt. Давайте подробно разберём все основные спецификаторы и их применение, чтобы вы могли протестировать вывод своих типов с разными вариантами.
Спецификаторы указываются внутри фигурных скобок {} и состоят из базового символа (например, ?, x, b) и, опционально, модификаторов (ширина, выравнивание, точность и т.д.). Каждый спецификатор связан с определённым трейтом форматирования из std::fmt. Вот полный список:
{} — Трейт DisplayDisplay.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: 3, y: 4 };
println!("{}", p); // (3, 4)
}
{:?} — Трейт Debug#[derive(Debug)].#[derive(Debug)]
struct Point { x: i32, y: i32 }
fn main() {
let P = Point { x: 3, y: 4 };
println!("{:?}", p); // Point { x: 3, y: 4 }
}
{:#?} — Трейт Debug (Pretty Print){:?}, но с "красивым" многострочным форматированием.#[derive(Debug)]
struct Point { x: i32, y: i32 }
fn main() {
let p = Point { x: 3, y: 4 };
println!("{:#?}", p);
// Point {
// x: 3,
// y: 4,
// }
}
{:b} — Трейт Binaryfn main() {
let n = 5;
println!("{:b}", n); // 101
}
{:o} — Трейт Octalfn main() {
let n = 8;
println!("{:o}", n); // 10
}
{:x} — Трейт LowerHexfn main() {
let n = 255;
println!("{:x}", n); // ff
}
{:X} — Трейт UpperHex{:x}, но с большими буквами (A-F).fn main() {
let n = 255;
println!("{:X}", n); // FF
}
{:p} — Трейт Pointerfn main() {
let x = 42;
println!("{:p}", &x); // 0x7ffc1a2b4b4c (адрес зависит от выполнения)
}
{:e} — Трейт LowerExpe.fn main() {
let n = 1234.567;
println!("{:e}", n); // 1.234567e3
}
{:E} — Трейт UpperExp{:e}, но с большой буквой E.fn main() {
let n = 1234.567;
println!("{:E}", n); // 1.234567E3
}
Спецификаторы можно дополнять модификаторами для управления шириной, выравниванием, точностью и заполнением. Они записываются перед основным символом (например, {:5} или {:.2}).
{:} )fn main() {
println!("{:5}", 42); // " 42" (3 пробела слева)
}
<, ^, >)<: Выравнивание влево.^: Выравнивание по центру.>: Выравнивание вправо (по умолчанию).fn main() {
println!("{:<5}", 42); // "42 "
println!("{:^5}", 42); // " 42 "
println!("{:>5}", 42); // " 42"
}
{:} )fn main() {
println!("{:0>5}", 42); // "00042"
println!("{:*>5}", 42); // "***42"
}
{:.precision})fn main() {
let n = 3.14159;
println!("{:.2}", n); // "3.14"
let s = "Hello, world!";
println!("{:.5}", s); // "Hello"
}
+)+ для положительных чисел.fn main() {
println!("{:+}", 42); // "+42"
println!("{:+}", -42); // "-42"
}
#)0b, 0o, 0x) для {:b}, {:o}, {:x}, {:X}.fn main() {
println!("{:x}", 255); // "ff"
println!("{:#x}", 255); // "0xff"
}
Вы можете комбинировать всё вышеперечисленное для сложного форматирования:
fn main() {
let n = 42;
println!("{:0>8b}", n); // "00101010" (двоичный, ширина 8, заполнение нулями)
let f = 3.14159;
println!("{:>10.2}", f); // " 3.14" (ширина 10, 2 знака после запятой)
let s = "Rust";
println!("{:*^10}", s); // "***Rust****" (ширина 10, выравнивание по центру, заполнение *)
}
Чтобы выполнить совет "Проверяйте вывод с разными спецификаторами ({}, {:?}, {:x}, etc.) для всех ваших типов", реализуйте несколько трейтов для своей структуры и протестируйте их с разными спецификаторами. Пример:
use std::fmt;
struct Number(i32);
impl fmt::Display for Number {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl fmt::Debug for Number {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Num({})", self.0)
}
}
impl fmt::Binary for Number {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:b}", self.0)
}
}
impl fmt::LowerHex for Number {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:x}", self.0)
}
}
fn main() {
let num = Number(42);
println!("{}", num); // 42
println!("{:?}", num); // Num(42)
println!("{:b}", num); // 101010
println!("{:x}", num); // 2a
println!("{:0>8b}", num); // 00101010
println!("{:#x}", num); // 0x2a
}
Rust предоставляет богатый набор спецификаторов формата, связанных с трейтами Display, Debug, Binary, LowerHex, UpperHex и другими. Комбинируя их с модификаторами (ширина, выравнивание, точность), вы можете настроить вывод под любые нужды — от отладки до пользовательских интерфейсов. Для тестирования ваших типов реализуйте несколько трейтов и экспериментируйте с разными комбинациями спецификаторов — это поможет вам глубже понять их возможности и ограничения!