0Pricing
Learn Rust Coding · Урок

Надёжная обработка ошибок с помощью `Result`

Реализуйте идиоматичную обработку ошибок с помощью перечисления `Result`, оператора `?` и пользовательских типов ошибок для создания устойчивых приложений.

«Надёжная обработка ошибок с помощью `Result`» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 3 из 3. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 3 уроков всего.

Обработка ошибок в Rust

Ошибки — часть программирования. В Rust различают два основных типа: восстановимые и невосстановимые ошибки.

  • Невосстановимые ошибки обычно указывают на ошибки в программе и приводят к её остановке с помощью panic!.
  • Восстановимые ошибки означают, что что-то пошло не так, но ситуацию можно обработать, например если файл не найден.

Rust помогает корректно обрабатывать восстановимые ошибки.

`panic!` против `Result`

Когда возникает невосстановимая ошибка, Rust вызывает макрос panic!. Он разворачивает стек и завершает работу программы, обычно выводя сообщение об ошибке.

Для восстановимых ошибок Rust использует перечисление Result. Это перечисление сообщает, завершилась ли операция успешно (Ok) или завершилась ошибкой (Err), позволяя программе решить, что делать дальше.

Перечисление `Result`

Перечисление Result концептуально определяется так:

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

  • T обозначает тип значения, возвращаемого при успешном выполнении.
  • E обозначает тип ошибки, возвращаемой при сбое.

Это мощный способ явно указать, что функция может завершиться ошибкой.

Сопоставление с `Result`

Самый распространённый способ обработать Result — использовать выражение match. Оно позволяет выполнять разные блоки кода в зависимости от того, содержит ли Result значение Ok или Err.

Попробуйте запустить этот пример, который пытается преобразовать строку в число:

fn parse_and_add(text: &str) -> Result<i32, std::num::ParseIntError> {
    text.parse::<i32>()
}

fn main() {
    let num_str = "123";
    let result = parse_and_add(num_str);

    match result {
        Ok(num) => println!("Parsed number: {}", num),
        Err(e) => println!("Error parsing: {}", e),
    }

    let bad_str = "abc";
    let bad_result = parse_and_add(bad_str);

    match bad_result {
        Ok(num) => println!("Parsed number: {}", num),
        Err(e) => println!("Error parsing: {}", e),
    }
}

Извлечение значений из Result (осторожно!)

Иногда Вы уверены, что операция не завершится ошибкой, или хотите, чтобы при ошибке программа завершилась. Методы вроде unwrap() и expect() извлекают значение Ok.

  • unwrap(): возвращает значение Ok или вызывает панику, если получено Err.
  • expect("message"): работает так же, как unwrap(), но позволяет указать собственное сообщение для паники.

Используйте эти методы умеренно — главным образом в тестах или когда сбой действительно невосстановим.

Знакомство с оператором `?`

Писать выражения match для каждого Result может быть утомительно. Оператор ? предоставляет краткий способ распространения ошибок.

Если поставить его после выражения, возвращающего Result, оператор ? выполняет два действия:

  • Если Result содержит Err, он немедленно возвращает Err из текущей функции.
  • Если Result содержит Ok, он извлекает значение Ok и продолжает выполнение.

Оператор `?` в действии

Оператор ? делает функции, которые могут завершиться ошибкой, гораздо более лаконичными. Помните: функция, использующая ?, сама должна возвращать Result (или Option).

Посмотрите, как ? упрощает обработку ошибок в этом примере чтения файла:

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?; // Propagates error if file not found
    let mut s = String::new();
    f.read_to_string(&mut s)?; // Propagates error if read fails
    Ok(s)
}

fn main() {
    // Create a dummy file for the example to work
    // In a real scenario, this file might not exist
    std::fs::write("hello.txt", "CoddyKit User").unwrap();

    match read_username_from_file() {
        Ok(username) => println!("Username: {}", username),
        Err(e) => println!("Error reading username: {}", e),
    }

    // Clean up the dummy file
    std::fs::remove_file("hello.txt").unwrap();
}

Пользовательские типы ошибок

Иногда встроенные типы ошибок недостаточно информативны. Вы можете определить собственные типы ошибок с помощью перечислений, чтобы представить разные варианты сбоев в приложении.

Это делает сообщения об ошибках понятнее и позволяет вызывающему коду по-разному обрабатывать конкретные ошибки.

Обычно создают перечисление, содержащее все возможные ошибки, которые может вызвать функция или модуль.

Использование `?` с пользовательскими ошибками

Чтобы оператор ? автоматически преобразовывал низкоуровневую ошибку, например io::Error, в Ваш пользовательский тип ошибки, пользовательское перечисление ошибок должно реализовать трейт From для этой низкоуровневой ошибки.

Так Rust узнаёт, как преобразовать один тип ошибки в другой, благодаря чему распространение ошибок становится бесшовным.

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

#[derive(Debug)]
enum MyError {
    Io(io::Error),
    Parse(std::num::ParseIntError)
}

impl From<io::Error> for MyError {
    fn from(error: io::Error) -> Self {
        MyError::Io(error)
    }
}

impl From<std::num::ParseIntError> for MyError {
    fn from(error: std::num::ParseIntError) -> Self {
        MyError::Parse(error)
    }
}

fn read_config(path: &str) -> Result<i32, MyError> {
    let mut file = File::open(path)?; // io::Error converted to MyError::Io
    let mut contents = String::new();
    file.read_to_string(&mut contents)?; // io::Error converted to MyError::Io
    let value = contents.trim().parse::<i32>()?; // ParseIntError converted to MyError::Parse
    Ok(value)
}

fn main() {
    // Create a dummy file for the example
    std::fs::write("config.txt", "123").unwrap();

    match read_config("config.txt") {
        Ok(val) => println!("Config value: {}", val),
        Err(e) => println!("Failed to read config: {:?}", e),
    }

    std::fs::remove_file("config.txt").unwrap(); // Clean up

    // Example with a non-existent file
    match read_config("non_existent.txt") {
        Ok(val) => println!("Config value: {}", val),
        Err(e) => println!("Failed to read config: {:?}", e),
    }
}

Проверьте понимание

Рассмотрите следующий фрагмент кода на Rust. Каким будет результат, если файл data.txt НЕ существует?

Итоги: надёжная обработка ошибок

Вы освоили надёжную обработку ошибок в Rust!

  • Перечисление Result: различает Ok(T) для успешного выполнения и Err(E) для ошибки.
  • match: основной способ обработки вариантов Result.
  • Оператор ?: мощное сокращение для распространения значений Err вверх по стеку вызовов.
  • Пользовательские ошибки: позволяют определять собственные типы ошибок с помощью перечислений для ясности и особой обработки.
  • Трейт From: обеспечивает бесшовное преобразование низкоуровневых ошибок в пользовательские типы ошибок при использовании ?.

Далее изучите расширенные возможности системы типов!

Часто задаваемые вопросы

Урок «Надёжная обработка ошибок с помощью `Result`» бесплатный?

Да — полный текст урока «Надёжная обработка ошибок с помощью `Result`» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 3 уроков всего.

Чему я научусь в уроке «Надёжная обработка ошибок с помощью `Result`»?

Реализуйте идиоматичную обработку ошибок с помощью перечисления `Result`, оператора `?` и пользовательских типов ошибок для создания устойчивых приложений. Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Learn Rust Coding?

Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 3.

Сколько времени занимает урок «Надёжная обработка ошибок с помощью `Result`»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Learn Rust Coding?

Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Организация кода с помощью модулей
  2. Управление зависимостями с помощью пакетов
  3. Надёжная обработка ошибок с помощью `Result`
← Назад к Learn Rust Coding