В Rust форматирование вывода осуществляется с помощью макросов, таких как println!
, format!
, write!
и других, которые используют спецификаторы формата внутри фигурных скобок {}
. Эти спецификаторы определяют, как данные будут представлены в текстовом виде, и зависят от трейтов форматирования из модуля std::fmt
. Давайте подробно разберём все основные спецификаторы и их применение, чтобы вы могли протестировать вывод своих типов с разными вариантами.
Спецификаторы указываются внутри фигурных скобок {}
и состоят из базового символа (например, ?
, x
, b
) и, опционально, модификаторов (ширина, выравнивание, точность и т.д.). Каждый спецификатор связан с определённым трейтом форматирования из std::fmt
. Вот полный список:
{}
— Трейт Display
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)
}
{:?}
— Трейт 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}
— Трейт Binary
fn main() {
let n = 5;
println!("{:b}", n); // 101
}
{:o}
— Трейт Octal
fn main() {
let n = 8;
println!("{:o}", n); // 10
}
{:x}
— Трейт LowerHex
fn main() {
let n = 255;
println!("{:x}", n); // ff
}
{:X}
— Трейт UpperHex
{:x}
, но с большими буквами (A-F).fn main() {
let n = 255;
println!("{:X}", n); // FF
}
{:p}
— Трейт Pointer
fn main() {
let x = 42;
println!("{:p}", &x); // 0x7ffc1a2b4b4c (адрес зависит от выполнения)
}
{:e}
— Трейт LowerExp
e
.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
и другими. Комбинируя их с модификаторами (ширина, выравнивание, точность), вы можете настроить вывод под любые нужды — от отладки до пользовательских интерфейсов. Для тестирования ваших типов реализуйте несколько трейтов и экспериментируйте с разными комбинациями спецификаторов — это поможет вам глубже понять их возможности и ограничения!