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

Раздел 5: Утилиты

Содержание: 1. std::cmp: Сравнение и упорядочивание 2. std::fmt: Форматирование и вывод 3. std::convert: Преобразование типов 4. std::mem: Низкоуровневые операции с памятью Практические советы Пример: Комбинирование утилит Упражнение Заключение

В этом разделе мы рассмотрим утилитные модули стандартной библиотеки Rust: std::cmp, std::fmt, std::convert и std::mem. Эти модули не относятся к конкретным задачам, таким как работа с файлами или коллекциями, а предоставляют общие инструменты для сравнения, форматирования, преобразования типов и управления памятью. Лекция подойдёт как новичкам, так и опытным разработчикам, желающим глубже понять внутренние механизмы Rust. Мы разберём каждый модуль с примерами, нюансами и практическими советами.


1. std::cmp: Сравнение и упорядочивание

Модуль std::cmp предоставляет инструменты для сравнения и упорядочивания значений. Он включает трейты и функции, которые помогают определить, больше ли одно значение другого, равны ли они, или как их отсортировать.

Основные компоненты:

Пример: использование max и clamp:

use std::cmp;

fn main() {
    let a = 5;
    let b = 10;
    
    // Находим максимум
    let max_val = cmp::max(a, b);
    println!("Максимум: {}", max_val); // 10
    
    // Ограничиваем значение
    let clamped = cmp::clamp(a, 6, 8);
    println!("Ограничено между 6 и 8: {}", clamped); // 6
}
    

Комментарии:

Трейты сравнения

Для пользовательских типов можно реализовать трейты:

use std::cmp::{PartialEq, PartialOrd, Ordering};

#[derive(Debug)]
struct Item {
    value: i32,
}

impl PartialEq for Item {
    fn eq(&self, other: &Self) -> bool {
        self.value == other.value
    }
}

impl PartialOrd for Item {
    fn partial_cmp(&self, other: &Self) -> Option {
        Some(self.value.cmp(&other.value))
    }
}

fn main() {
    let item1 = Item { value: 5 };
    let item2 = Item { value: 10 };
    println!("item1 < item2? {}", item1 < item2); // true
}
    

Комментарии:

Заметка: Используйте #[derive(PartialEq, Eq, PartialOrd, Ord)] для автоматической реализации, если тип простой.


2. std::fmt: Форматирование и вывод

Модуль std::fmt отвечает за форматирование данных для вывода. Он используется в макросах вроде println! и предоставляет трейты для настройки отображения значений. Этот модуль — ключ к тому, чтобы ваш код мог "говорить" с пользователем или разработчиком через консоль, логи или другие интерфейсы.

Основные трейты:

Пример: кастомный 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 предназначен для разработчиков, а Display — для конечных пользователей. Не путайте их назначение.

2.1 Трейт Debug

Трейт Debug предназначен для вывода данных в формате, удобном для разработчиков. Он автоматически реализуется для большинства встроенных типов и может быть легко добавлен к пользовательским типам с помощью атрибута #[derive(Debug)]. Используется с макросом {:?} в println! или format!.

Пример: автоматическая реализация 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 самостоятельно:

use std::fmt;

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

impl fmt::Debug for Point {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Point")
         .field("x_coord", &self.x)
         .field("y_coord", &self.y)
         .finish()
    }
}

fn main() {
    let p = Point { x: 3, y: 4 };
    println!("Точка: {:?}", p); // Точка: Point { x_coord: 3, y_coord: 4 }
}

Комментарии:

Заметка: Используйте Debug для отладки, так как он показывает внутреннюю структуру данных, даже если она не предназначена для пользователя.

2.2 Трейт Display

Трейт Display предназначен для вывода данных в формате, удобном для конечных пользователей. В отличие от Debug, он не реализуется автоматически и требует ручной настройки.

Пример: кастомный 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.3 Трейт Formatter

Formatter — это низкоуровневый инструмент, используемый внутри реализаций Debug, Display и других трейтов форматирования. Он предоставляет доступ к параметрам форматирования (ширина, выравнивание, точность) и позволяет полностью контролировать процесс вывода.

Пример: использование Formatter с настройкой выравнивания:

use std::fmt;

struct Number(i32);

impl fmt::Display for Number {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if f.align().is_some() {
            // Если задано выравнивание, используем его
            write!(f, "{:>5}", self.0) // Выравнивание вправо, ширина 5
        } else {
            write!(f, "{}", self.0) // Обычный вывод
        }
    }
}

fn main() {
    let num = Number(42);
    println!("Обычный: {}", num);         // Обычный: 42
    println!("Выровненный: {:>10}", num); // Выровненный:        42
}

Комментарии:

Более сложный пример с Debug и Formatter:

use std::fmt;

struct Matrix {
    rows: usize,
    cols: usize,
    data: Vec,
}

impl fmt::Debug for Matrix {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Matrix")
         .field("dimensions", &format_args!("{}x{}", self.rows, self.cols))
         .field("data", &self.data)
         .finish()
    }
}

fn main() {
    let matrix = Matrix {
        rows: 2,
        cols: 3,
        data: vec![1, 2, 3, 4, 5, 6],
    };
    println!("Матрица: {:?}", matrix);
    // Матрица: Matrix { dimensions: "2x3", data: [1, 2, 3, 4, 5, 6] }
}

Комментарии:

Предупреждение: Formatter требует осторожности при работе с параметрами формата — неправильная логика может привести к панике или некорректному выводу.

Таким образом, Debug удобен для быстрой отладки, Display — для красивого вывода, а Formatter даёт полный контроль над процессом форматирования.


3. std::convert: Преобразование типов

Модуль std::convert предоставляет трейты для преобразования типов: From, Into и TryFrom. Это позволяет явно и безопасно конвертировать значения.

Основные трейты:

Пример: использование From и TryFrom:

use std::convert::{From, TryFrom};

#[derive(Debug)]
struct EvenNumber(i32);

impl From for EvenNumber {
    fn from(value: i32) -> Self {
        EvenNumber(value * 2) // Всегда чётное
    }
}

impl TryFrom for EvenNumber {
    type Error = &'static str;
    fn try_from(value: i32) -> Result {
        if value % 2 == 0 {
            Ok(EvenNumber(value))
        } else {
            Err("Число нечётное")
        }
    }
}

fn main() {
    let num1 = EvenNumber::from(5); // 10
    println!("From: {:?}", num1);
    
    match EvenNumber::try_from(7) {
        Ok(num) => println!("TryFrom: {:?}", num),
        Err(e) => println!("Ошибка: {}", e), // Ошибка: Число нечётное
    }
}
    

Комментарии:


4. std::mem: Низкоуровневые операции с памятью

Модуль std::mem предоставляет функции для работы с памятью на низком уровне: получение размера типов, замена значений, освобождение памяти и т.д.

Основные функции:

Пример: работа с памятью:

use std::mem;

fn main() {
    // Размер типа
    println!("Размер i32: {} байт", mem::size_of::()); // 4
    
    // Обмен значениями
    let mut a = 5;
    let mut b = 10;
    mem::swap(&mut a, &mut b);
    println!("a: {}, b: {}", a, b); // a: 10, b: 5
    
    // Замена значения
    let mut x = String::from("Hello");
    let old = mem::replace(&mut x, String::from("World"));
    println!("Старое: {}, Новое: {}", old, x); // Hello, World
}
    

Комментарии:

Заметка: Используйте mem::drop, чтобы явно освободить значение до конца области видимости.


Практические советы


Пример: Комбинирование утилит

Программа, использующая все модули:

use std::cmp::Ordering;
use std::fmt;
use std::convert::From;
use std::mem;

#[derive(Debug)]
struct Data {
    value: i32,
}

impl fmt::Display for Data {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Data({})", self.value)
    }
}

impl From for Data {
    fn from(value: i32) -> Self {
        Data { value }
    }
}

impl PartialEq for Data {
    fn eq(&self, other: &Self) -> bool {
        self.value == other.value
    }
}

impl PartialOrd for Data {
    fn partial_cmp(&self, other: &Self) -> Option {
        Some(self.value.cmp(&other.value))
    }
}

fn main() {
    let mut d1 = Data::from(5);
    let mut d2 = Data::from(10);
    
    // Сравнение
    println!("d1 < d2? {}", d1 < d2);
    
    // Форматирование
    println!("d1: {}", d1);
    
    // Обмен
    mem::swap(&mut d1, &mut d2);
    println!("После обмена: d1={}, d2={}", d1, d2);
}
    

Этот пример объединяет сравнение, форматирование, преобразование и работу с памятью.


Упражнение

Напишите программу, которая:

  1. Определяет структуру Range с полями min и max (тип i32).
  2. Реализует Display для вывода в формате [min..max].
  3. Реализует From<(i32, i32)> для создания из кортежа.
  4. Реализует PartialOrd, сравнивая по min.
  5. Использует mem::replace для замены значения и выводит старое и новое.

Подсказка: Используйте write! в fmt и cmp для сравнения.


Заключение

Модули std::cmp, std::fmt, std::convert и std::mem — это утилиты, которые делают код на Rust выразительным, безопасным и гибким. Они покрывают базовые потребности в сравнении, выводе, преобразованиях и управлении памятью. Освоив их, вы сможете писать более читаемый и эффективный код. Далее мы рассмотрим другие аспекты стандартной библиотеки, такие как работа с потоками.

Выполните упражнение или переходите к следующему разделу!