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