Обработка ошибок в Rust: try/catch и не только — полный гайд

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему Rust не использует try/catch?

В большинстве языков программирования (JavaScript, Python, Java) обработка ошибок строится на механизме исключений и конструкции try/catch. Однако в Rust такой подход отсутствует. Вместо этого разработчики используют мощную систему типов, которая делает ошибки частью сигнатуры функции. Это не случайность, а продуманное архитектурное решение: Rust стремится к предсказуемости и безопасности на этапе компиляции, исключая скрытые падения программы.

В этой статье мы разберём:

  • Почему Rust отказался от try/catch;
  • Как работают основные инструменты: Option, Result, unwrap(), expect();
  • Что такое оператор ? (вопросительный знак);
  • Когда использовать panic! и макрос try! (устаревший);
  • Современные практики обработки ошибок в Rust 2024.

1. Основы: Option и Result — два главных типа

Тип Option: когда значение может отсутствовать

Option<T> — это перечисление (enum), которое представляет либо значение (Some(T)), либо его отсутствие (None). Он идеально подходит для ситуаций, когда ошибка в классическом понимании не происходит, но результат может быть пустым.

fn найти_пользователя(id: u32) -> Option<String> {
    let база = vec!["Alice".to_string(), "Bob".to_string()];
    if id < база.len() {
        Some(база[id].clone())
    } else {
        None
    }
}

fn main() {
    match найти_пользователя(1) {
        Some(имя) => println!("Найден: {}", имя),
        None => println!("Пользователь не найден"),
    }
}

Тип Result: успех или ошибка

Result<T, E> — основной инструмент для обработки ошибок. Он также является enum: Ok(T) (успех) или Err(E) (ошибка).

use std::fs::File;
use std::io::Read;

fn прочитать_файл(путь: &str) -> Result<String, std::io::Error> {
    let mut файл = File::open(путь)?;
    let mut содержимое = String::new();
    файл.read_to_string(&mut содержимое)?;
    Ok(содержимое)
}

2. Оператор ? (вопросительный знак) — замена try/catch

Оператор ? — это основной способ проброса ошибок наверх в Rust. Он заменяет громоздкие match конструкции и делает код чистым. Если результат — Ok, значение извлекается и присваивается переменной. Если Err — функция немедленно возвращает эту ошибку.

use std::fs;
use std::io;

fn копировать_файл(источник: &str, назначение: &str) -> io::Result<()> {
    let данные = fs::read(источник)?; // если ошибка — выход
    fs::write(назначение, &данные)?;  // если ошибка — выход
    Ok(())
}

fn main() -> io::Result<()> {
    копировать_файл("source.txt", "dest.txt")?;
    println!("Файл скопирован успешно");
    Ok(())
}

Важно: оператор ? работает только в функциях, которые возвращают Result, Option или типы, реализующие трейт FromResidual.

3. panic!, unwrap и expect — когда ошибка фатальна

Макрос panic!

panic!() вызывает аварийное завершение программы (stack unwinding). Используется только в ситуациях, когда продолжение выполнения невозможно или бессмысленно: ошибки в логике программы, нарушение инвариантов, тесты.

fn деление(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Деление на ноль!");
    }
    a / b
}

Методы unwrap() и expect()

unwrap() — быстрый способ получить значение из Option или Result. Если внутри None или Err — программа паникует. expect() делает то же самое, но позволяет задать собственное сообщение об ошибке.

fn main() {
    let число: Option<i32> = Some(42);
    println!("{}", число.unwrap()); // 42

    let ошибка: Option<i32> = None;
    // println!("{}", ошибка.unwrap()); // panic!
    println!("{}", ошибка.expect("Ожидалось число, но получили None"));
}

Рекомендация: Используйте unwrap() только в прототипах, тестах или когда вы на 100% уверены, что ошибки не будет. В production-коде предпочтительнее expect() с осмысленным сообщением.

4. Макрос try! — устаревший, но встречающийся

В ранних версиях Rust (до 1.13) оператора ? не существовало. Вместо него использовался макрос try!. Сейчас он считается устаревшим, но вы всё ещё можете встретить его в старом коде.

// Старый стиль (не рекомендуется)
fn старая_функция() -> Result<(), std::io::Error> {
    let f = try!(File::open("foo.txt"));
    Ok(())
}

// Современный стиль
fn новая_функция() -> Result<(), std::io::Error> {
    let f = File::open("foo.txt")?;
    Ok(())
}

Если вы поддерживаете легаси-проект на Rust, замените try! на оператор ? — это улучшит читаемость и совместимость с современными идиомами.

5. Создание собственных типов ошибок

Для сложных проектов неудобно использовать стандартные типы ошибок. Rust позволяет создавать собственные типы, реализуя трейт std::error::Error.

use std::fmt;

#[derive(Debug)]
enum MyError {
    NotFound,
    PermissionDenied(String),
    IoError(std::io::Error),
}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) ->                

Блоги

Рекомендаций по книгам