Глава 18: Обзор стандартной библиотеки

Раздел 2: Спецификаторы форматирования в Rust

Содержание: Основные спецификаторы формата в Rust Модификаторы форматирования Комбинирование спецификаторов и модификаторов Тестирование с вашими типами Заключение

В Rust форматирование вывода осуществляется с помощью макросов, таких как println!, format!, write! и других, которые используют спецификаторы формата внутри фигурных скобок {}. Эти спецификаторы определяют, как данные будут представлены в текстовом виде, и зависят от трейтов форматирования из модуля std::fmt. Давайте подробно разберём все основные спецификаторы и их применение, чтобы вы могли протестировать вывод своих типов с разными вариантами.


Основные спецификаторы формата в Rust

Спецификаторы указываются внутри фигурных скобок {} и состоят из базового символа (например, ?, x, b) и, опционально, модификаторов (ширина, выравнивание, точность и т.д.). Каждый спецификатор связан с определённым трейтом форматирования из std::fmt. Вот полный список:

1. {} — Трейт Display

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)
}

2. {:?} — Трейт 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 }
}

3. {:#?} — Трейт 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,
    // }
}

4. {:b} — Трейт Binary

fn main() {
    let n = 5;
    println!("{:b}", n); // 101
}

5. {:o} — Трейт Octal

fn main() {
    let n = 8;
    println!("{:o}", n); // 10
}

6. {:x} — Трейт LowerHex

fn main() {
    let n = 255;
    println!("{:x}", n); // ff
}

7. {:X} — Трейт UpperHex

fn main() {
    let n = 255;
    println!("{:X}", n); // FF
}

8. {:p} — Трейт Pointer

fn main() {
    let x = 42;
    println!("{:p}", &x); // 0x7ffc1a2b4b4c (адрес зависит от выполнения)
}

9. {:e} — Трейт LowerExp

fn main() {
    let n = 1234.567;
    println!("{:e}", n); // 1.234567e3
}

10. {:E} — Трейт UpperExp

fn main() {
    let n = 1234.567;
    println!("{:E}", n); // 1.234567E3
}

Модификаторы форматирования

Спецификаторы можно дополнять модификаторами для управления шириной, выравниванием, точностью и заполнением. Они записываются перед основным символом (например, {:5} или {:.2}).

1. Ширина ({:})

fn main() {
    println!("{:5}", 42); // "   42" (3 пробела слева)
}

2. Выравнивание (<, ^, >)

fn main() {
    println!("{:<5}", 42);  // "42   "
    println!("{:^5}", 42);  // " 42  "
    println!("{:>5}", 42);  // "   42"
}

3. Заполнение ({:})

fn main() {
    println!("{:0>5}", 42); // "00042"
    println!("{:*>5}", 42); // "***42"
}

4. Точность ({:.precision})

fn main() {
    let n = 3.14159;
    println!("{:.2}", n);      // "3.14"
    let s = "Hello, world!";
    println!("{:.5}", s);      // "Hello"
}

5. Знак (+)

fn main() {
    println!("{:+}", 42);  // "+42"
    println!("{:+}", -42); // "-42"
}

6. Префикс для чисел (#)

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 и другими. Комбинируя их с модификаторами (ширина, выравнивание, точность), вы можете настроить вывод под любые нужды — от отладки до пользовательских интерфейсов. Для тестирования ваших типов реализуйте несколько трейтов и экспериментируйте с разными комбинациями спецификаторов — это поможет вам глубже понять их возможности и ограничения!