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

Раздел 10: Управление ошибками и опциональными значениями

Содержание: 1. std::option: Тип Option<T> 2. std::result: Тип Result<T, E> 3. std::error: Определение пользовательских ошибок Практические советы Пример: Комбинирование модулей Упражнение Заключение

В этом разделе мы разберём модули стандартной библиотеки Rust, связанные с управлением ошибками и опциональными значениями: std::option, std::result и std::error. Эти инструменты являются основой для безопасной и выразительной работы с ситуациями, где данные могут отсутствовать или операции могут завершиться неудачей. Лекция подойдёт как новичкам, так и опытным разработчикам, желающим углубить свои знания. Мы рассмотрим каждый модуль с примерами, нюансами и практическими советами.


1. std::option: Тип Option<T>

Модуль std::option определяет тип Option<T> — перечисление, которое представляет значение, которое может быть либо присутствующим (Some(T)), либо отсутствующим (None). Это ключевая часть философии Rust, заменяющая null-указатели других языков.

Определение:

enum Option<T> {
    Some(T),
    None,
}
    

Основные методы:

Пример: базовые операции с Option:

fn main() {
    let some_value = Some(42);
    let no_value: Option<i32> = None;
    
    // Извлечение
    println!("Значение: {}", some_value.unwrap()); // 42
    println!("С запасным: {}", no_value.unwrap_or(0)); // 0
    
    // Преобразование
    let squared = some_value.map(|x| x * x);
    println!("Квадрат: {:?}", squared); // Some(1764)
}
    

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

Заметка: Используйте match или if let для безопасной обработки Option вместо unwrap.


2. std::result: Тип Result<T, E>

Модуль std::result определяет тип Result<T, E> — перечисление для представления успешного результата (Ok(T)) или ошибки (Err(E)). Это основной механизм обработки ошибок в Rust.

Определение:

enum Result<T, E> {
    Ok(T),
    Err(E),
}
    

Основные методы:

Пример: работа с Result:

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("Деление на ноль"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    let res1 = divide(10, 2);
    let res2 = divide(10, 0);
    
    // Извлечение
    println!("Результат 1: {}", res1.unwrap()); // 5
    println!("Результат 2: {}", res2.unwrap_or(-1)); // -1
    
    // Преобразование
    let doubled = res1.map(|x| x * 2);
    println!("Удвоено: {:?}", doubled); // Ok(10)
}
    

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


3. std::error: Определение пользовательских ошибок

Модуль std::error предоставляет трейт Error для создания пользовательских типов ошибок. Это позволяет интегрировать ошибки в экосистему Rust, включая оператор ?.

Трейт Error:

Пример: пользовательская ошибка:

use std::error::Error;
use std::fmt;

#[derive(Debug)]
struct MathError {
    message: String,
}

impl fmt::Display for MathError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Ошибка вычисления: {}", self.message)
    }
}

impl Error for MathError {}

fn compute(a: i32) -> Result<i32, Box<dyn Error>> {
    if a < 0 {
        Err(MathError {
            message: String::from("Отрицательное число")
        }.into())
    } else {
        Ok(a * 2)
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let res = compute(-5)?;
    println!("Результат: {}", res); // Не дойдёт сюда
    Ok(())
}
    

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

Предупреждение: Использование unwrap в реальном коде нежелательно — предпочитайте обработку ошибок.


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


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

Программа для разбора числа:

use std::error::Error;
use std::fmt;

#[derive(Debug)]
struct ParseError(String);

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Ошибка разбора: {}", self.0)
    }
}

impl Error for ParseError {}

fn parse_number(s: &str) -> Result<i32, Box<dyn Error>> {
    let num = s.parse::<i32>().map_err(|e| ParseError(e.to_string()))?;
    if num < 0 {
        return Err(ParseError("Отрицательное число".into()).into());
    }
    Ok(num)
}

fn main() -> Result<(), Box<dyn Error>> {
    let input = Some("42");
    let result = input.ok_or("Нет ввода")?
        .parse::<i32>()
        .map_err(|e| ParseError(e.to_string()))?;
    println!("Число: {}", result);
    Ok(())
}
    

Этот пример использует Option для проверки ввода, Result для разбора и Error для пользовательской ошибки.


Упражнение

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

  1. Принимает строку через аргументы командной строки.
  2. Определяет пользовательский тип ошибки CalcError с полем message.
  3. Проверяет, является ли строка числом с помощью parse (Result).
  4. Если число присутствует (Option), вычисляет его квадрат, иначе возвращает ошибку.
  5. Выводит результат или сообщение об ошибке.

Подсказка: Используйте std::env::args и ?.


Заключение

Модули std::option, std::result и std::error обеспечивают мощную и безопасную систему управления ошибками и опциональными значениями в Rust. Они позволяют писать надёжный код, избегая непредвиденных сбоев. Освоив их, вы сможете эффективно обрабатывать нештатные ситуации. Далее мы рассмотрим файловый ввод-вывод.

Выполните упражнение или двигайтесь дальше!