std::option: Тип Option<T>
2. std::result: Тип Result<T, E>
3. std::error: Определение пользовательских ошибок
Практические советы
Пример: Комбинирование модулей
Упражнение
Заключение
В этом разделе мы разберём модули стандартной библиотеки Rust, связанные с управлением ошибками и опциональными значениями: std::option, std::result и std::error. Эти инструменты являются основой для безопасной и выразительной работы с ситуациями, где данные могут отсутствовать или операции могут завершиться неудачей. Лекция подойдёт как новичкам, так и опытным разработчикам, желающим углубить свои знания. Мы рассмотрим каждый модуль с примерами, нюансами и практическими советами.
std::option: Тип Option<T>Модуль std::option определяет тип Option<T> — перечисление, которое представляет значение, которое может быть либо присутствующим (Some(T)), либо отсутствующим (None). Это ключевая часть философии Rust, заменяющая null-указатели других языков.
Определение:
enum Option<T> {
Some(T),
None,
}
Основные методы:
unwrap, unwrap_or: Извлечение значения.map, and_then: Преобразование содержимого.is_some, is_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)
}
Комментарии:
unwrap паникует, если это None.unwrap_or предоставляет значение по умолчанию.map применяет функцию к Some, оставляя None неизменным.Заметка: Используйте match или if let для безопасной обработки Option вместо unwrap.
std::result: Тип Result<T, E>Модуль std::result определяет тип Result<T, E> — перечисление для представления успешного результата (Ok(T)) или ошибки (Err(E)). Это основной механизм обработки ошибок в Rust.
Определение:
enum Result<T, E> {
Ok(T),
Err(E),
}
Основные методы:
unwrap, expect: Извлечение значения с паникой при ошибке.map, and_then: Преобразование успеха.map_err: Преобразование ошибки.Пример: работа с 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)
}
Комментарии:
unwrap паникует при Err, expect позволяет задать сообщение.map изменяет Ok, оставляя Err нетронутым.and_then полезен для цепочек операций, возвращающих Result.std::error: Определение пользовательских ошибокМодуль std::error предоставляет трейт Error для создания пользовательских типов ошибок. Это позволяет интегрировать ошибки в экосистему Rust, включая оператор ?.
Трейт Error:
description: Устаревший метод (заменён Display).source: Причина ошибки (опционально).Debug и Display.Пример: пользовательская ошибка:
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(())
}
Комментарии:
Box<dyn Error> позволяет возвращать разные типы ошибок.into преобразует MathError в Box<dyn Error>.? работает с типами, реализующими Error.Предупреждение: Использование unwrap в реальном коде нежелательно — предпочитайте обработку ошибок.
Option: Используйте and_then для цепочек операций, чтобы избежать вложенных match.Result: Применяйте ? для упрощения обработки ошибок в функциях.Error: Реализуйте Error для всех пользовательских ошибок и добавляйте контекст с помощью source.Box<dyn Error> в горячих циклах — это требует выделения памяти.Программа для разбора числа:
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 для пользовательской ошибки.
Напишите программу, которая:
CalcError с полем message.parse (Result).Option), вычисляет его квадрат, иначе возвращает ошибку.Подсказка: Используйте std::env::args и ?.
Модули std::option, std::result и std::error обеспечивают мощную и безопасную систему управления ошибками и опциональными значениями в Rust. Они позволяют писать надёжный код, избегая непредвиденных сбоев. Освоив их, вы сможете эффективно обрабатывать нештатные ситуации. Далее мы рассмотрим файловый ввод-вывод.
Выполните упражнение или двигайтесь дальше!