Философия обработки ошибок в Rust
Rust не использует исключения. Вместо этого ошибки — это значения, которые возвращаются из функций. Это делает ошибки явными и обрабатываемыми.
▸Типы ошибок
Result<T, E>
▸Определение
1enum Result<T, E> {2 Ok(T),3 Err(E),4}
▸Использование
1fn divide(a: f64, b: f64) -> Result<f64, String> {2 if b == 0.0 {3 Err("Division by zero".to_string())4 } else {5 Ok(a / b)6 }7}89fn main() {10 match divide(10.0, 2.0) {11 Ok(result) => println!("Result: {}", result),12 Err(e) => println!("Error: {}", e),13 }14}
Option<T>
▸Определение
1enum Option<T> {2 Some(T),3 None,4}
▸Использование
1fn find_user(id: u32) -> Option<User> {2 if id == 1 {3 Some(User { name: "Alice".to_string() })4 } else {5 None6 }7}89fn main() {10 match find_user(1) {11 Some(user) => println!("Found: {}", user.name),12 None => println!("User not found"),13 }14}
? Оператор
▸Обработка ошибок
1use std::fs;2use std::io;34fn read_config(path: &str) -> Result<String, io::Error> {5 let content = fs::read_to_string(path)?; // ? возвращает ошибку6 Ok(content)7}89// Аналогично с Option10fn first_word(s: &str) -> Option<&str> {11 let bytes = s.as_bytes();12 for (i, &item) in bytes.iter().enumerate() {13 if item == b' ' {14 return Some(&s[0..i]);15 }16 }17 None18}
▸Цепочки вызовов
1fn process() -> Result<(), Box<dyn std::error::Error>> {2 let config = fs::read_to_string("config.toml")?;3 let parsed: Config = toml::from_str(&config)?;4 let connection = db::connect(&parsed.database_url)?;56 Ok(())7}
Методы Result и Option
▸map и and_then
1// Result2let result: Result<i32, Error> = Ok(5);3let mapped = result.map(|x| x * 2); // Ok(10)45// Option6let opt: Option<i32> = Some(5);7let mapped = opt.map(|x| x * 2); // Some(10)89// and_then (flatMap)10let result = Ok(5)11 .and_then(|x| if x > 0 { Ok(x * 2) } else { Err("negative") });
###unwrap и expect
1// unwrap — паникует при ошибке2let value = some_option.unwrap();34// expect — паникует с сообщением5let value = some_option.expect("Value should exist");67// Безопасные альтернативы8let value = some_option.unwrap_or(0);9let value = some_option.unwrap_or_else(|| compute_default());
Кастомные ошибки
▸Определение ошибок
1use std::fmt;23#[derive(Debug)]4enum AppError {5 NotFound(String),6 InvalidInput(String),7 DatabaseError(String),8}910impl fmt::Display for AppError {11 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {12 match self {13 AppError::NotFound(s) => write!(f, "Not found: {}", s),14 AppError::InvalidInput(s) => write!(f, "Invalid input: {}", s),15 AppError::DatabaseError(s) => write!(f, "Database error: {}", s),16 }17 }18}1920impl std::error::Error for AppError {}
▸Thiserror
1use thiserror::Error;23#[derive(Error, Debug)]4enum AppError {5 #[error("not found: {0}")]6 NotFound(String),78 #[error("invalid input: {0}")]9 InvalidInput(String),1011 #[error("database error")]12 DatabaseError(#[from] sqlx::Error),13}
Anyhow
▸Упрощение ошибок
1use anyhow::{Context, Result};23fn process() -> Result<()> {4 let config = fs::read_to_string("config.toml")5 .context("Failed to read config")?;67 let parsed: Config = toml::from_str(&config)8 .context("Failed to parse config")?;910 Ok(())11}
Паттерны обработки ошибок
▸Match
1match some_operation() {2 Ok(value) => println!("Success: {}", value),3 Err(AppError::NotFound(s)) => println!("Not found: {}", s),4 Err(e) => println!("Other error: {}", e),5}
▸If let
1if let Ok(value) = some_operation() {2 println!("Success: {}", value);3}
▸Combining Results
1fn process_multiple() -> Result<()> {2 let a = operation_a()?;3 let b = operation_b()?;4 let c = operation_c()?;56 Ok(())7}
Тестирование ошибок
1#[test]2fn test_divide_by_zero() {3 let result = divide(10.0, 0.0);4 assert!(result.is_err());5 assert_eq!(result.unwrap_err(), "Division by zero");6}78#[test]9fn test_find_user_not_found() {10 let result = find_user(999);11 assert!(result.is_none());12}
Best Practices
▸Используйте ? вместо unwrap
1// Плохо2let value = some_option.unwrap();34// Хорошо5let value = some_option?;
▸Контекст для ошибок
1use anyhow::Context;23let config = fs::read_to_string("config.toml")4 .context("Failed to read config file")?; // Добавляет контекст
Заключение
Обработка ошибок в Rust — это система типов, гарантирующая обработку всех возможных ошибок. Result и Option, ? оператор и кастомные ошибки — основные инструменты. На собеседовании спрашивают про разницу Result и Option, как использовать ? оператор, и когда что использовать.