Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Тема
Описание
Доп.

📌 Новый раздел

  • 👾 Best practice
  • Варианты применения

Ошибки делятся на две категории:

  • recoverable (обрабатываемые) Ошибки, которые могут произойти при нормальной работе и из которых можно восстановиться. Возвращаем Result, чтобы вызывающий сам решил — повторить, сообщить пользователю, завершить и т.д. Используются для внешних ситуаций.

    Примеры:

    • файл не найден
    • нет соединения с сервером
    • пользователь ввёл неверные данные
  • unrecoverable (необрабатываемые) Ошибки, которые не должны случаться вообще, т.е. программа оказалась в некорректном состоянии (нарушен инвариант, логическая ошибка). Тут паника (panic!), потому что программа уже не может продолжать корректную работу.

    Примеры:

    • индекс за пределами массива
    • деление на ноль
    • нарушен внутренний контракт API (вызвали функцию не так, как она была задумана, то есть не соблюли её правила (предусловия).

Библиотеки и приложения по разному реализуют ошибки

nick.groenen.me/posts/rust-error-handling

  • Библиотеки должны сосредоточиться на создании осмысленных, структурированных типов ошибок. Это позволяет приложениям легко различать различные случаи ошибок.
  • Приложения в основном потребляют ошибки.
  • Библиотеки могут захотеть преобразовать ошибки из одного типа в другой. Ошибка ввода-вывода, вероятно, должна быть заключена в тип ошибки высокого уровня, предоставляемый библиотекой.
  • Если библиотеки возвращают ошибки, приложения решают, будут ли и как эти ошибки форматироваться и отображаться для пользователей.

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

Библиотеки против приложений

Существует различие между кодом, который написан для повторного использования в библиотеке, и кодом, который формирует приложение верхнего уровня.

Код, написанный для библиотеки, не может предсказать среду, в которой используется код, поэтому предпочтительнее выдавать конкретную, подробную информацию об ошибке и оставлять вызывающему пользователю возможность выяснить, как использовать эту информацию. Это склоняется к enum вложенным ошибкам в стиле, описанным ранее (а также позволяет избежать зависимости от anyhow в публичном API библиотеки).

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

При работе с неоднородными базовыми типами ошибок решите, необходимо ли сохранять эти типы.

  • Если нет, рассмотрите возможность использования anyhow для обертывания подошибок в коде приложения.
  • Если да, закодируйте их в enum и предоставьте преобразования. Рассмотрите возможность использования thiserror для помощи в этом. Рассмотрите возможность использования anyhow контейнера для удобной идиоматической обработки ошибок в коде приложения.

Распечатать информацию по коду ошибки

error-index.html#E0004

cargo --explain 0004

rm -fr target Cargo.lock
cargo check

трассировка

Для получения более подробной информации вы можете посмотреть трассировку установив переменную среды RUST_BACKTRACE:

$ RUST_BACKTRACE=1 ./diverges или $ RUST_BACKTRACE=1 cargo run

просмотра обратных трассировок во время выполнения


use std::backtrace::Backtrace;
 
fn main() {
    std::env::set_var("RUST_BACKTRACE", "Hi I'm backtraceㅎㅎㅎ");
    println!("{}", Backtrace::capture());
}


use std::{
    backtrace::{Backtrace, BacktraceStatus::*},
    panic,
};
 
fn main() {
    panic::set_hook(Box::new(|_| {
        println!("Panicked! Trying to get a backtrace...");
        let backtrace = Backtrace::capture();                            
        match backtrace.status() {                                       
            Disabled => println!("Backtrace isn't enabled, sorry"),
            Captured => println!("Here's the backtrace!!\n{backtrace}"),
            Unsupported => println!("No backtrace possible, sorry"),     
            // Do some database shutting down stuff 
        }
    }));
 
    std::env::set_var("RUST_BACKTRACE", "0");                            
    panic!();
}

синтаксис ?

fn do_something_that_might_fail(i: i32) -> Result<f32,String> {
    if i == 42 {
        Ok(13.0)
    } else {
        Err(String::from("this is not the right number"))
    }
}

fn foo2()-> Result<(),String> {
// с ?
   let v:f32 = do_something_that_might_fail(42)?;
// map_err
  fn foo2()-> Result<(),MyError> {
    do_something_that_might_fail(42).map_err(|e|MyError(e))?   

// без ?
    let res = do_something_that_might_fail(42);
    let v:f32;
    if res.is_ok(){
        v = res.unwrap();
    }else{
        return Err(res.err().unwrap());
    }
   Ok(())
}

Trait std::ops::Try вернуть в Option либо Result

nightly-only

#![feature(try_trait_v2)]
use std::ops::Try;
fn simple<T: Copy, R: Try<Output = T>>(value:T) -> (impl Try<Output = T>,impl Try<Output = T>){
     (Some(value),Ok::<T,Box<dyn std::error::Error>>(value))
}
fn foo(path: &std::path::Path) -> std::result::Result<(), Box<dyn std::error::Error>> {
    Err(Box::new( std::io::Error::new(std::io::ErrorKind::Other, "oh no!"))
} 

is<T>(&self) -> bool - проверка ошибки на соответствие T

downcast_ref - Возвращает некоторую ссылку на внутреннее значение, если оно имеет тип T


fn main(){
     match something(path) {
        Ok(sum) => println!("the sum is {}", sum),
        Err(err) => {
            if let Some(e) = err.downcast_ref::() {
               // ...
            } else if let Some(e) = err.downcast_ref::() {...}
        }
    }
}


fn main(){
    let err_io = std::io::Error::new(std::io::ErrorKind::Other, "oh no!");
    let mut custom_error:Result<(), &dyn std::error::Error> = std::result::Result::Err(&err_io);

    match custom_error {
        Err(ref mut e) => {
            if e.is::(){
                if let Some(err_io) = e.downcast_ref::(){
                    println!("{}",err_io.kind());// other error
                } 
            }
        },
        _ => {}
    }
}

Box<dyn std::error::Error>

Все типы ошибок должны реализовывать трейт std::error::Error поэтому мы можем возвращать с помощью динамической диспетчеризации Box<dyn error::Error>

use std::io::Read;
fn sum_file(path: &std::path::Path) -> std::result::Result<i32, Box<dyn std::error::Error>> {
    let mut file = std::fs::File::open(path)?; //  std::io::Error -> Box<dyn std::error::Error>
    let mut contents = String::new();
    file.read_to_string(&mut contents)?; //  std::io::Error -> Box<dyn std::error::Error>
    let mut sum = 0;
    for line in contents.lines() {
        sum += line.parse::<i32>()?; // std::num::ParseIntError -> Box<dyn std::error::Error>
    }
    Ok(sum)
}

// Недостаток, это потеря типа возвращаемой ошибки.
// (но если вызывающая сторона знает подробности реализации нашей функции, она все равно может обрабатывать различные типы ошибок, используя метод, downcast_ref())

fn main(){
  match sum_file(path) {
        Ok(sum) => println!("the sum is {}", sum),
        Err(err) => {
            if let Some(e) = err.downcast_ref::<std::io::Error>() {...
            } else if let Some(e) = err.downcast_ref::<std::num::ParseIntError>() {...}
       }
  }
}

use std::error::Error;
fn foo() -> Result<(), Box<dyn Error>> {
    std::fs::File::open("not-here")?; // io::Error
    Err("oh noooo!")?;   // &str
    Err("I broke it :<".to_owned())?;  // String
    Err("nop".into())
}

Свой тип ошибки


use std::error::Error;
use my_library::*;
mod my_library{
    use std::io::Read;
    #[derive(Debug)]
    pub enum SumFileError {
        Io(std::io::Error),
        Parse(std::num::ParseIntError),
        ZeroSum(ZeroSumKind),
        CustomSum(String), // или назвать General(String)
    } 
   // Также хорошей идеей будет реализоватьFromчерта для всех типов подошибок
   // внедрение From позволяет добиться еще большей краткости, поскольку Оператор вопросительного знака ? 
   // автоматически выполнит все необходимые From преобразования, устраняя необходимость в .map_err()
    impl std::convert::From for SumFileError {
        fn from(err: std::io::Error) -> Self {
            SumFileError::Io(err)
        }
    }
    impl std::convert::From for SumFileError {
        fn from(err: std::num::ParseIntError) -> Self {
            SumFileError::Parse(err)
        }
    }
    impl std::fmt::Display for SumFileError {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            match self {
                SumFileError::Io(err) => write!(f, "{}", err),
                SumFileError::Parse(err) => write!(f, "{}", err),
                SumFileError::ZeroSum(err) => write!(f, "sum file error: {}", err),
                SumFileError::CustomSum(err) => write!(f, "sum file error: {}", err),
            }
        }
    }
    #[derive(Debug)]
    pub struct ZeroSumKind;
    impl std::error::Error for ZeroSumKind {}
    impl std::fmt::Display for ZeroSumKind {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "ZeroSumKind is here!")
        }
    }
    // Также имеет смысл переопределить source()реализацию по умолчанию для легкого доступа к вложенным ошибкам:
    impl std::error::Error for SumFileError {
        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
            match self {
                SumFileError::Io(err) => Some(err),
                SumFileError::Parse(err) => Some(err),
                SumFileError::ZeroSum(err) => Some(err),
                SumFileError::CustomSum(err) => None,
            }
        }
    }
    pub fn sum_file(path: &std::path::Path) -> std::result::Result {
        let mut file = std::fs::File::open(path)?; // std::io::Error -> SumFileError
        let mut contents = String::new();
        file.read_to_string(&mut contents)?; // std::io::Error -> SumFileError
        let mut sum = 0;
        for line in contents.lines() {
            sum += line.parse::()?; // std::num::ParseIntError -> SumFileError
        }
        if sum == 0{
            return Err(SumFileError::ZeroSum(ZeroSumKind));// явное создание внутренненр типа ошибки
        }
        if sum == 5{
            return Err(SumFileError::CustomSum("Value must not be 5".to_owned()));// явное создание внутренненр типа ошибки
        }
        Ok(sum)
    }
}
fn main() {
    let res:std::result::Result = sum_file(std::path::Path::new("src/main.rs"));
    match res{
        Err(SumFileError::Io(e)) => {println!("{:?}",e);},
        Err(SumFileError::Parse(e)) => {println!("{:?}",e);},
        Err(SumFileError::ZeroSum(e)) => {println!("{:?}",e);},
        Err(SumFileError::CustomSum(e)) => {println!("{:?}",e);},
        Ok(_)=>{}
    };    
    let res:std::result::Result = sum_file(std::path::Path::new("src/main.rs"));
    match res{
        Err(e) => {
            println!("{:?} msg:{}",e,e.to_string());
             
            if let Some(e) = e.source().unwrap().downcast_ref::(){// use std::error::Error
                let kind:&std::num::IntErrorKind = e.kind();// std::num::IntErrorKind::InvalidDigit
                println!("{:?}",kind);
            }
        },
        _ =>{}
    }
   // From/Into
   match "...".parse::(){
        Err(parse_err) => {
            let err: SumFileError = SumFileError::Parse(parse_err.clone());
            let err: SumFileError = parse_err.clone().into();
            let err: SumFileError = SumFileError::from(parse_err);
            // Display 
            println!("{}",err);// invalid digit found in string
        },
        _ =>{}
   }
   let err:SumFileError = SumFileError::ZeroSum(ZeroSumKind);
   println!("{}",err);// sum file error: ZeroSumKind is here!
}

Когда использовать crate thiserror и crate anyhow или crate eyre или crate color-eyre

rust-error-handling

Если ты хочешь писать либу/модуль и ошибки — это твой API, то тебе подойдёт thiserror, если ты просто хочешь как есть красивенько вывести ошибки на консоль пользователю, то anyhow или приложение. Оба модуля разработаны одним автором и именно потому что в разработке есть разные требования и не нужно их смешивать в один модуль, но в итоге ты можешь решить использовать оба модуля в своём проекте. Используйте Anyhow, если вас не волнует, какой тип ошибки возвращают ваши функции (главное реализующих std::error::Error), вы просто хотите, чтобы это было легко.

crate thiserror занял больше времени компиляции чем с ручной реализацией, т.е. для библиотек он не подходит, только для приложений.

crate color-eyre это форк anyhow, который дает вам больше контроля над форматом генерируемых сообщений об ошибках. Рекомендуется, если вы собираетесь представлять сообщения об ошибках конечным пользователям. В противном случае anyhow проще.

thiserror

Свой тип ошибки с помощью thiserror

docs.rs/thiserror

crates/thiserror

rust-error-handling


use my_library::*;
mod my_library{
    use std::io::Read;
    use super::*;

    #[derive(thiserror::Error, Debug)]
    pub enum SumFileError {
        #[error(transparent)]
        Io(#[from] std::io::Error),
        #[error("Path: {0}, I/O error: {1}")]
        IoMetadata(std::path::PathBuf, #[source] io::Error),
        #[error(transparent)]
        Parse(#[from] std::num::ParseIntError),
        #[error("sum file error: {0}")]
        ZeroSum(ZeroSumKind),
        #[error("sum file error: {0}")]
        CustomSum(String),
    } 

    #[derive(thiserror::Error, Debug)]
    #[error("ZeroSumKind is here!")]
    pub struct ZeroSumKind;

    pub fn sum_file(path: &std::path::Path) -> std::result::Result {
        let mut file = std::fs::File::open(path)?; // std::io::Error -> SumFileError
        let mut contents = String::new();
        file.read_to_string(&mut contents)?; //  std::io::Error -> SumFileError
        let mut sum = 0;
        for line in contents.lines() {
            sum += line.parse::().map_err(|e|{e})?; //  std::num::ParseIntError -> SumFileError
        }
        if sum == 0{
            return Err(SumFileError::ZeroSum(ZeroSumKind).into());// явное создание внутренненр типа ошибки
        }
        if sum == 5{
            return Err(SumFileError::CustomSum("Value must not be 5".to_owned()).into());// явное создание внутренненр типа ошибки
        }
        Ok(sum)
    }
}
fn main() {
    let res:std::result::Result = sum_file(std::path::Path::new("src/main.rs"));
     match res{
        Err(SumFileError::Io(e)) => {println!("{:?}",e);},
        Err(SumFileError::Parse(e)) => {
            println!("{:?}",e);// ParseIntError { kind: InvalidDigit }
            let kind:&std::num::IntErrorKind = e.kind();
            println!("{:?}",kind);// InvalidDigit
        },
        Err(SumFileError::ZeroSum(e)) => {println!("{:?}",e);},
        Err(SumFileError::CustomSum(e)) => {println!("{:?}",e);},
        Ok(_)=>{}
    };  
   
    // From/Into
    match "...".parse::(){
        Err(parse_err) => {
            let _err:SumFileError = parse_err.clone().into();
            let _err: SumFileError = SumFileError::Parse(parse_err.clone());
            let err: SumFileError = SumFileError::from(parse_err);
             // Display 
            println!("{}",err);// invalid digit found in string
        },
        _ =>{}
   }
   // Display 
   let err:SumFileError = SumFileError::ZeroSum(ZeroSumKind);
   println!("{}",err);// sum file error: ZeroSumKind is here!
 }  

Использования форматированного вывод ошибки Display с помощью eprintln! или anyhow::Result

[dependencies]
anyhow = "1.0"
thiserror = "1.0"

use std::path::PathBuf;
pub mod error{
    use std::{io, path::PathBuf};
    use std::process::ExitCode;

    #[derive(thiserror::Error, Debug)]
    pub enum Error {
        #[error("I/O error: {1}, path: {0}")]
        ReadDir(PathBuf, #[source] io::Error),

        #[error("Error of the standard output: {0}")]
        Io(io::Error),
        #[error("File not found: {0}")]
        FileNotFound(PathBuf),
        #[error("Json parse: {0}")]
        RequestClientJsonParse(String),
        #[error("File size exceeded the limit of {0} bytes.")]
        FileSizeExceeded(u64),
        #[error("No data to replace the contents of the file")]
        EmptyContent,
    }
}
// Used:
///
// Для демонстрация ошибки `Error::ReadDir`
// ```
// cargo run -- readdir
// ```
/// По умолчанию при выводе в стандартный поток вывода stdout (println!), работатет отладочный Debug
/// поэтому то что написано в `#[error("I/O error: {1}, path: {0}")]` не будет показано.
/// А просто сработает реализация Debug для типа ошибки "Error: ReadDir("readdir", Custom { kind: Other, error: "your message" })".
/// Что бы сработал форматированный Display надо использовать eprintln! "Error: I/O error: your message, path: readdir"
/// или anyhow::Result
fn main2() -> std::result::Result<(), error::Error> {
    let args: Vec = std::env::args().collect(); 
    if args.len() < 2 {
        return  Err(error::Error::EmptyContent);
        //return Err(std::io::Error::other("args are not found"));
    }
    let arg = args[1].clone();
    let result: Result<(), error::Error> = match arg.as_str() {
        "readdir" =>  Err(error::Error::ReadDir(PathBuf::from(arg),std::io::Error::other("your message"))),
        _ => Err(error::Error::EmptyContent)
    };
    if let Err(e) = result {
        eprintln!("Error: {}", e); // Используем Display
    }
    Ok(())
}
/// Вариант перенаправления ошибки в Display через anyhow::Result
/// этот вариант даже показывет информацию из ` #[source]`
/// ```
/// Error: I/O error: your message, path: readdir
/// 
/// Caused by:
///     your message
/// ```
fn main() -> anyhow::Result<()> {
    let args: Vec = std::env::args().collect(); 
    if args.len() < 2 {
        return  Err(error::Error::EmptyContent.into());
    }
    let arg = args[1].clone();
    let result: anyhow::Result<()> = match arg.as_str() {
        "readdir" =>  Err(error::Error::ReadDir(PathBuf::from(arg),std::io::Error::other("your message")).into()),
        
        _ => Err(error::Error::EmptyContent.into())
    };
    result
}

anyhow

crate anyhow

Благодаря anyhow::Result нет нужды писать std::result::Result<(), SumFileError>, можно просто anyhow::Result<()>, для более чистого кода Благодаря anyhow::Context появляется метод .context(msg) для вывода деталей ошибки

Используйте Anyhow, если вас не волнует, какой тип ошибки возвращают ваши функции (главное реализующих std::error::Error), вы просто хотите, чтобы это было легко.

Решить как показать ошибку пользователю.


fn main() -> anyhow::Result<()>{
    return Err(anyhow::anyhow!("Missing attribute: {}", 3));
   // anyhow::bail!("Missing attribute: {}", 3);
   
    let any:anyhow::Error = anyhow::anyhow!("Missing attribute: {}", 3);
    return Err(any.context(format!("Context attribute: {}",3)));
  
    return Err(anyhow::Error::new(SumFileError::ZeroSum(ZeroSumKind)).context(format!("Context attribute: {}",3)));
    // return Err(anyhow::Error::msg(format!("Missing attribute: {}",3)));
  
   let err:std::result::Result<_,SumFileError> = Ok(SumFileError::ZeroSum(ZeroSumKind));
   let _ = err?;// from anyhow::Result
   let _ = sum_file(std::path::Path::new("src/main.rs"))?;

    // From/Into
    match "...".parse::(){
        Err(parse_err) => {
            let _err:anyhow::Error = parse_err.clone().into();
            let err: anyhow::Error = anyhow::Error::from(parse_err);
            // Display 
            println!("{}",err);// invalid digit found in string
        },
        _ =>{}
    }
   Ok(())
}

crate color-eyre

Основные возможности color-eyre:

  • Цветной вывод ошибок: Ошибки выделяются с использованием цветов, что облегчает их чтение и анализ.
  • Поддержка трассировки стека: При включении RUST_BACKTRACE=1 ошибки сопровождаются полной трассировкой стека.
  • Интеграция с контекстом ошибок: Удобно работать с цепочками ошибок через методы вроде wrap_err().

use color_eyre::eyre::{Result, eyre};
use std::fs;

fn read_file(path: &str) -> Result {
    let content = fs::read_to_string(path).wrap_err_with(|| format!("Failed to read file: {}", path))?;
    Ok(content)
}

fn main() -> Result<()> {
    // Устанавливаем обработчик ошибок `color-eyre`
    color_eyre::install()?;

    // Пробуем открыть файл
    match read_file("example.txt") {
        Ok(content) => println!("File content: {}", content),
        Err(e) => eprintln!("Error: {:?}", e),
    }

    Ok(())
}

crate eyre

ответвление от anyhow с поддержкой настраиваемых отчетов об ошибках

Аналог error-stack:


fn main_with_eyre() -> eyre::Result<()> {
    let _ = sum_file(std::path::Path::new("src/main.rs"))?;// from eyre::Result

    let res:eyre::Result = sum_file(std::path::Path::new("src/main.rs")).map_err(|e|e.into());
    /*
    Format backtrace {:?}
    Format Debug {:#?}

    println!("{:?}",res);
        Err(invalid digit found in string
        Location:
            /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library/core/src/convert/mod.rs:717:9)

    println!("{:#?}",res);
        Err(
            Parse(
                ParseIntError {
                    kind: InvalidDigit,
                },
            ),
        )
    */
   if false{
       // return Err(eyre::eyre!("Missing attribute: {}", 3));
          return Err(eyre::eyre!(SumFileError::ZeroSum(ZeroSumKind)));
   }
   Ok(())
}

Используем макрос для удобного формирования ошибки, с двумя или тремя параметрами

Из книги: Rust Web Programming - Third Edition. By Maxwell Flitton


#[macro_export]
macro_rules! safe_eject {
    ($e:expr, $err_status:expr) => {
        $e.map_err(|x| NanoServiceError::new(
            x.to_string(),
            $err_status)
        )
    };
    ($e:expr, $err_status:expr, $message_context:expr) => {
        $e.map_err(|x| NanoServiceError::new(
                format!("{}: {}", $message_context, x.to_string()),
                $err_status
            )
        )
    };
}
use error2::{NanoServiceError, NanoServiceErrorStatus};
pub mod error2{
    use std::fmt;
    use thiserror::Error;
    use serde::{Deserialize, Serialize};

    #[derive(Serialize, Deserialize, Debug, Error)]
    pub struct NanoServiceError {
        pub message: String,
        pub status: NanoServiceErrorStatus
    }
    #[derive(Error, Debug, Serialize, Deserialize, PartialEq)]
    pub enum NanoServiceErrorStatus {
        #[error("Requested resource was not found")]
        NotFound,
        #[error("You are forbidden to access requested resource.")]
        Forbidden,
        #[error("Unknown Internal Error")]
        Unknown,
        #[error("Bad Request")]
        BadRequest,
        #[error("Conflict")]
        Conflict,
        #[error("Unauthorized")]
        Unauthorized
    }
    impl NanoServiceError {
        pub fn new(message: String, status: NanoServiceErrorStatus) -> NanoServiceError {
            NanoServiceError { message,status }
        }
    }
    impl fmt::Display for NanoServiceError {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "{}", self.message)
        }
    }
}
// Used:
///
// Для демонстрация ошибки `Error::ReadDir`
// ```
// cargo run -- badrequest
// ```
// Используем макрос для удобного формирования ошибки, с двумя или тремя параметрами
// Вывожим ошибку страндарный поток вывода через Display благодаря преобразованию в anyhow::Result 
fn main() -> anyhow::Result<()> {
    let args: Vec = std::env::args().collect(); 
    if args.len() < 2 {
        return Err(NanoServiceError::new("args are not found".to_owned(), NanoServiceErrorStatus::NotFound).into());
        //return Err(std::io::Error::other("args are not found"));
    }
    let arg = args[1].clone();
     match arg.as_str() {
        "badrequest" =>  {
            let file = safe_eject!(std::fs::OpenOptions::new().open("file_name"), NanoServiceErrorStatus::BadRequest, "add context")?;
            Ok(())
        },
        _ => Err(NanoServiceError::new("unknown type error".to_owned(),NanoServiceErrorStatus::Conflict).into())
    }
}

Зачем придумали panic которая завершает работу процесса?

Она предназначена для ошибок программиста и этот тип ошибок сложно обрабатывать и не нужно.

«Ошибка программиста» - программа не должна была попасть в это состояние при корректном использовании. То есть ошибка не вызвана внешними факторами (файла нет, сеть отвалилась, пользователь ввёл мусор), а произошла потому, что код написан неправильно — нарушен контракт.

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

Что в библиотеке, что в приложении которое использует библиотеку - выдаем ошибку если она там планировалась, и кидаем исключение если ПО входи в некорректное состояние, нарушается инвариант.

Инвариант = внутреннее логическое правило, которое всегда должно выполняться, иначе программа работает неправильно (баг).

Документируйте паники и unsafe ограничения

Документируйте panic и unsafe ограничения: если есть входные данные, которые вызывают панику функции, документируйте (в # Panics разделе) предварительные условия, которые требуются для избежания panic!.

Аналогичным образом документируйте (в # Safety разделе) любые требования к unsafe код.


/// # Panics 
/// ..... ....

fn main(){
 ...
}

crate no_panic

перекладывание ответственности постоянно быть бдительным за возникновения паники с себя на автоматические инструменты экосистемы Rust

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


use no_panic::no_panic;

#[no_panic]
fn demo(s: &str) -> &str {
    &s[1..]
}

fn main() {
    println!("{}", demo("input string"));
}

Когда использовать «panic!»

При его выполнении программа выводит на консоль сообщение об ошибке, очищает стек и завершает выполнение программы.

  • Происходит критическая ошибка, которая не может быть обработана;
  • Приложение не может продолжать работу без существенной потери данных или нарушения логики программы;
  • Выполнение программы должно быть прервано во избежание дальнейшего повреждения файлов или данных.

Так какая же альтернатива panic! для обработки состояний ошибки? Для библиотечного кода лучшей альтернативой является сделать ошибку чьей-то чужой проблемой , вернув Result с соответствующим типом ошибки. Это позволяет пользователю библиотеки принимать собственные решения о том, что делать дальше, что может включать передачу проблемы следующему абоненту в очереди через оператора ?

panic! или не panic!

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

Когда вы возвращаете значение Result, вы делегируете принятие решения вызывающему коду. Вызывающий код может попытаться выполнить восстановление способом, который подходит в данной ситуации, или же он может решить, что из ошибки в Err нельзя восстановиться и вызовет panic!, превратив вашу исправимую ошибку в неисправимую.

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

  • unwind - Размотайте stack вызовов в случае паники.
  • abort - Завершите процесс в случае паники.

lurklurk.org/effective-rust/panic

Игнорируют настройку паники

  • tests
  • benchmarks
  • build scripts
  • proc macros

File Cargo.toml:

[profile.dev]
panic = "abort"

Когда вы настраиваете профиль в Cargo.toml с параметром panic = "abort", функция std::panic::catch_unwind не сможет перехватить паникование (panic) Также бывает так, что некоторые целевые платформы (например, WebAssembly) всегда abort (прерывается) при возникновении паники, независимо от настроек компилятора или проекта.

Более тонкая проблема, которая выявляется при преодолении паники, заключается в следующем:безопасность исключений : если паника происходит в середине операции над структурой данных, она устраняет любые гарантии того, что структура данных осталась в самосогласованном состоянии.

В режиме FFI небезопасно иметь режим panic = "unwind" так как разворачивание стека (unwind) предназначено для внутреннего использования в Rust. Внешний язык (например, C) не ожидает разворачивания стека Rust и не может корректно обработать его. Это может привести к неопределённому поведению, утечкам памяти или другим серьёзным ошибкам.

Паника будет разворачивать стек, запускать деструкторы и обеспечивать очистку памяти. Abort не делает этого и полагается на ОС для правильной очистки.

Попробуйте запустить следующую программу как обычно, так и с помощью Cargo.toml:

[profile.dev]
panic = "abort"

struct Foo(i32);
impl Drop for Foo {
    fn drop(&mut self) {
        println!("Dropping {:?}!", self.0);
    }
}
fn main() {
    let foo = Foo(1);
    panic!("Aaaaaaahhhhh!");
}

Контроль panic!

Если очень хочется, панику можно перехватить: std::panic::catch_unwind

thread::panicking


fn main(){
   // Если очень хочется, панику можно перехватить:

    // 1. при присоединении потока:
    let handle = std::thread::spawn(|| panic!("boom"));
    match handle.join() {
        Ok(()) => println!("ok"),
        Err(_) => println!("panicked"),
    }

    // 2. где угодно:
    let result = std::panic::catch_unwind(|| panic!("boom"));
    match result {
        Ok(()) => println!("ok"),
        Err(_) => println!("panicked"),
    }
   // thread::panicking проверяет, есть ли паника (только в реализации Drop обьекта)
   // panic::resume_unwind переподнимает панику

}
// RUST_BACKTRACE=short cargo run --bin=test

std::panic::set_hook

Хуки паник работают только в том потоке, в котором они были установлены. Использование хуков не предотвращает завершение программы при панике, если только вы не используете std::panic::catch_unwind для перехвата паники.

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

Использование

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


use std::panic;
fn main() {
    panic::set_hook(Box::new(|info| {
        println!("A panic occurred: {}", info);
    }));

    panic!("This is a test panic!");
}

std::panic::take_hook

Функция take_hook позволяет получить текущий обработчик паник и вернуть его в виде значения Box<dyn Fn(&PanicInfo) + 'static + Sync + Send>. Если вы хотите сохранить текущий хук, чтобы позже его использовать, или восстановить стандартное поведение, эта функция пригодится.

Использование

take_hook используется для временной замены обработчика паник или возврата к стандартному.


use std::panic;
fn main() {
    // Сохранение текущего хука
    let original_hook = panic::take_hook();

    // Установка нового хука
    panic::set_hook(Box::new(|info| {
        println!("Temporary panic hook: {}", info);
    }));

    // Паника с временным хуком
    panic!("This is a temporary panic!");

    // Восстановление исходного хука
    panic::set_hook(original_hook);
}

Результат: Сначала выводится сообщение от временного хука, а после восстановления оригинального хука паники будут обрабатываться так, как это было настроено изначально.

  • is_some(), is_none() - проверка
  • is_some_and() - проверка ф-цией значения
  • and() логическое AND - возвращает res, если результат равен Some, в противном случае возвращает значение None
  • or() - логическое ИЛИ
  • or_else() - логическое ИЛИ Если нет результата вызывает ф-цию
  • xor(Option<T>) -> Option<T>

fn main(){
    let x: Option = Some(2);
    assert_eq!(x.is_some(), true);

    let x: Option = None;
    assert_eq!(x.is_none(), true);
}


fn main(){
    let x: Option = Some(2);
    assert_eq!(x.is_some_and(|x| x > 1), true);
}


fn main(){
    let x = Some(2);
    let y = Some("foo");
    assert_eq!(x.and(y), Some("foo"));
}


fn main(){
    let x = None;
    let y = Some(100);
    assert_eq!(x.or(y), Some(100));
}


fn main(){
    fn vikings() -> Option<&'static str> { Some("vikings") }
    assert_eq!(None.or_else(vikings), Some("vikings"));
}

filter() - результат Some(T) или None в зависимости от предиката true или false

std::convert::identity - фильтр Some


fn main(){
    fn is_even(n: &i32) -> bool {
            n % 2 == 0
    }
    assert_eq!(None.filter(is_even), None);
    assert_eq!(Some(3).filter(is_even), None);
    assert_eq!(Some(4).filter(is_even), Some(4));
}

Использование identity для сохранения Some вариантов итератора Option<T>


use std::convert::identity;
fn main(){
    let iter = vec![Some(1), None, Some(3)].into_iter();
    let filtered = iter.filter_map(identity).collect::>();
    assert_eq!(vec![1, 3], filtered);
}
  • take() - Принимает значение из Option, оставляя вместо него None. т.е. подмена значения в обход правил владения

  • get_or_insert() -> &mut T - Вставляет v в параметр, если он None, затем возвращает измененную ссылку на содержащееся значение.

  • get_or_insert_with() -> &mut T - Вставляет значение, вычисленное из f в параметр, если оно равно None, а затем возвращает изменчивую ссылку на содержащееся значение

  • replace() - подмена значения

  • insert(&mut self, value: T) -> &mut T - Вставляет значение


fn main(){
    let mut x = Some(2);
    let val = x.take();
    assert_eq!(x, None);

    аналог
    let val = std::mem::replace(&mut composers[0].name, None);
}


fn main(){
    let mut x = None;
    {
            let y: &mut u32 = x.get_or_insert(5);
            assert_eq!(y, &5);

            *y = 7;
    }
    assert_eq!(x, Some(7));
}


fn main(){
    let mut x = None;
    {
            let y: &mut u32 = x.get_or_insert_with(|| 5);
            assert_eq!(y, &5);

            *y = 7;
    }
    assert_eq!(x, Some(7));
}


fn main(){
    let mut x = Some(2);
    let old = x.replace(5);
    assert_eq!(x, Some(5));
    assert_eq!(old, Some(2));
}


fn main(){
    let mut opt = None;
    let val:&mut i32 = opt.insert(1);
    assert_eq!(*val, 1);
    assert!(opt.is_some());
    assert_eq!(opt.unwrap(), 1);
}
  • unzip() - Возвращает значение с деструкцией
  • zip() - как unzip но обратно
  • unwrap() - Возвращает значение или panic!
  • expect() - достаёт значение или panic! с сообщением
  • unwrap_or() - Возвращает содержащееся значение или значение по умолчанию.
  • unwrap_or_else() - (лениво оценивается) Возвращает содержащееся значение или значение по умолчанию.
  • unwrap_or_default() - Возвращает содержащееся значение или значение по умолчанию для этого типа

trait.Default.html#tymethod.default


fn main(){
    let x = Some((1, "hi"));
    assert_eq!(x.unzip(), (Some(1), Some("hi")));
}


fn main(){
    let x = Some("air"); // let x: Option<&str> = None;
    assert_eq!(x.unwrap(), "air");
}


fn main(){
    let x = Some("value");
    assert_eq!(x.expect("the world is ending"), "value");
    // let x: Option<&str> = None; x.expect("the world is ending");
}


fn main(){
    assert_eq!(Some("car").unwrap_or("bike"), "car");
    assert_eq!(None.unwrap_or("bike"), "bike");
}


fn main(){
    let k = 10;
    assert_eq!(Some(4).unwrap_or_else(|| 2 * k), 4);
    assert_eq!(None.unwrap_or_else(|| 2 * k), 20);
}


fn main(){
    let bad_year_from_input = "190blarg"; 
    let bad_year = bad_year_from_input.parse().ok().unwrap_or_default();
    assert_eq!(0, bad_year);
}

and_then()

  • мутирует данных если результат Some(T)
  • избавится от вложенных match, возвращает результирующее значение

fn sq(x: u32) -> Option { Some(x * x) }
fn nope(_: u32) -> Option { None }
fn main(){
    assert_eq!(Some(2).and_then(sq).and_then(sq), Some(16));
}


enum Food { CordonBleu, Steak, Sushi }
fn have_ingredients(food:Food)->Option{ Some(food)}
fn have_recipe(food:Food)->Option{ Some(food)}
fn cookable_v1(food: Food) -> Option {
    match have_ingredients(food) {
        None       => None,
        Some(food) => match have_recipe(food) {
            None       => None,
            Some(food) => Some(food),
        },
    }
}

 // или так
fn cookable_v2(food: Food) -> Option {
    have_ingredients(food).and_then(have_recipe)
}
fn main()-> Result<(),String> {
   cookable_v1(Food::CordonBleu);
   cookable_v2(Food::CordonBleu);
   Ok(())
}
  • map() - преобразует данные
  • map_or() - Применяет функцию к содержащемуся значению (если есть) или возвращает предоставленное значение по умолчанию (если нет).
  • map_or_else() - ленивый map
  • as_ref() - Конвертирует в ссылку Option<T> to Option<&T>
  • as_mut() - Конвертирует в mut ссылку Option<T> to Option<&mut T>
  • as_deref() - Конвертирует Option<T> (or &Option<T>)
  • as_deref_mut() - Конвертирует Option<T> (or &mut Option<T>)
fn main(){
    let maybe_some_string = Some(String::from("Hello, World!"));
    let maybe_some_len:Option<usize> = maybe_some_string.map(|s| s.len());
    assert_eq!(maybe_some_len, Some(13));  
}

fn main(){
    let x = Some("foo");
    assert_eq!(x.map_or(42, |v| v.len()), 3);

    let x: Option<&str> = None;
    assert_eq!(x.map_or(42, |v| v.len()), 42);
}

fn main(){
    let k = 21;
    let x = Some("foo");
    assert_eq!(  x.map_or_else(|| 2 * k  ,  |v| v.len())     , 3);
}

fn main(){
    let text: Option<String> = Some("Hello, world!".to_string());
    let text_length: Option<usize> = text.as_ref().map(|s| s.len());
    println!("still can print text: {:?}", text);
}
// Пример с Cow<'static, str>
pub struct Core {
    orm: Box<dyn Store>,
    pub user: Option<Cow<'static, str>>,
}

fn main(){
    self.user
            .as_ref()
            .map_or(Err(CustomError::Unauthorized), |_| {
....
}

fn main(){
    let mut x = Some(2);
    match x.as_mut() {
        Some(v) => *v = 42,
        None => {},
    }
    assert_eq!(x, Some(42));
}

#[derive(Debug)]
struct B(i32)
fn main() {
  let mut test:Option<B> = Some(B(88));
  let b_mut = test.as_mut().unwrap();
  b_mut.0 = 44;
  if let Some(b) = &test{
       assert_eq!(b.0,44); 
  }
  println!("{:?}",&test);// Some(B(44))
}

fn main(){
    let x: Option<String> = Some("hey".to_owned());
    let x: Option<&str> = x.as_deref();

    let mut x: Option<String> = Some("hey".to_owned());
    assert_eq!(x.as_deref_mut().map(|x| {
        x.make_ascii_uppercase();
        x
    }), Some("HEY".to_owned().as_mut_str()));
}
  • ok_or() - Преобразует Option<T> в Result<T, E>, Some(v) в Ok(v) и None в Err(err) (ok_or_else предпочтительней, ленивое)

  • transpose(self) -> Result<Option<T>, E> - Транспонирует Option из Result


fn main(){
    UserNum::new(num).ok_or(serde::de::Error::custom(format!(
                "`num` must be in range {} {}",
                 MIN_NUM, MAX_NUM
    )))
    UserNum::new(num).ok_or_else(|| serde::de::Error::custom(format!(
                "`num` must be in range {} {}",
                MIN_NUM, MAX_NUM
    )))
}


#[derive(Debug, Eq, PartialEq)]
struct SomeErr;
fn main(){
    let x: Result, SomeErr> = Ok(Some(5));
    let y: Option> = Some(Ok(5));
    assert_eq!(x, y.transpose());
}
  • cloned() - клонирует данные если реализован Copy и Clone
  • copied() - копирует если реализован Copy
  • flatten() -> Option<T> выравнивает вложенность Option<Option<T>> to Option<T>

fn main(){
    // Для типов в стеке copied() и cloned()
    let source = Some(&12);
    let r = source.copied().unwrap_or(0);
    let r2 = source.cloned().unwrap_or(0);
    println!("{r} {r2}");
     
    // Для типов в куче только cloned()
    let v = vec![1,2,3]; 
    let source = Some(&v); 
    let r = source.cloned().unwrap_or(v);
    println!("{:?}",r);
}


fn main(){
    let x: Option> = Some(Some(6));
    let x: Option = x.flatten()); // Some(6)
}
  • iter() - Возвращает итератор поверх возможного содержащегося значения.
  • iter_mut() - Возвращает изменяемый итератор поверх возможного содержащегося значения.

fn main(){
     let x = Some(4);
    assert_eq!(x.iter().next(), Some(&4));
}


fn main(){
    let mut x = Some(4);
    match x.iter_mut().next() {
        Some(v) => *v = 42,
         None => {},
    }
    assert_eq!(x, Some(42));
}

Ошибки в многопоточном асинхронные контексты требуют дополнительных ограничений type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;

Итерирование по Result

rust-by-example/error/iter_result

Примеры комбинаторов

combinators-on-result-type

  • is_ok() - Возвращает true, если результат равен Ok.
  • is_err() - Возвращает true, если результатом является Err.
  • and() - логическое AND Возвращает res, если результат равен Ok, в противном случае возвращает значение Err
  • or(res) - логическое ИЛИ Возвращает res, если результатом является Err, в противном случае возвращает значение Ok
  • or_else() - логическое ИЛИ Если нет результата вызывает функцию

fn main(){
    let x: Result = Ok(-3);
    assert_eq!(x.is_ok(), true);

    let x: Result = Err("Some error message");
    assert_eq!(x.is_err(), true);
}


fn main(){
    let x: Result = Ok(2);
    let y: Result<&str, &str> = Err("late error");
    assert_eq!(x.and(y), Err("late error"));

    let x: Result = Err("early error");
    let y: Result<&str, &str> = Ok("foo");
    assert_eq!(x.and(y), Err("early error"));
}


fn main(){
    let x: Result = Ok(2);
    let y: Result = Err("late error");
    assert_eq!(x.or(y), Ok(2));

    let x: Result = Err("early error");
    let y: Result = Ok(2);
    assert_eq!(x.or(y), Ok(2));
}


fn main(){
    fn sq(x: u32) -> Result { Ok(x * x) }
    fn err(x: u32) -> Result { Err(x) }

    assert_eq!(Ok(2).or_else(sq).or_else(sq), Ok(2));
    assert_eq!(Ok(2).or_else(err).or_else(sq), Ok(2));
    assert_eq!(Err(3).or_else(sq).or_else(err), Ok(9));
    assert_eq!(Err(3).or_else(err).or_else(err), Err(3));
}
  • ok() - Converts from Result<T, E> to Option<T>
  • err() - Converts from Result<T, E> to Option<E>
  • transpose(self) -> Option<Result<T, E>>

fn main(){
    let x: Result = Ok(2);
    assert_eq!(x.ok(), Some(2));
}


fn main(){
    let x: Result = Err("Nothing here");
    assert_eq!(x.ok(), None);
}


fn main(){
    let x: Result = Ok(2); 
    assert_eq!(x.err(), None);

    let x: Result = Err("Nothing here");
    assert_eq!(x.err(), Some("Nothing here"));
}


fn main(){
    // Транспонирует в Result из  Option
    #[derive(Debug, Eq, PartialEq)]
    struct SomeErr;

    let x: Result, SomeErr> = Ok(Some(5));
    let y: Option> = Some(Ok(5));
    assert_eq!(x.transpose(), y);
}
  • as_ref() - Converts from Result<T, E> to Result<&T, &E>
  • as_mut() - Converts from Result<T, E> to Result<&mut T, &mut E>

fn main(){
    #[derive(Debug)]
    struct A(pub i32);
    let a = A(8);
    let ref_var = &Some(a);
    // Для &Option нет методов, для преобразования в Option<&T> используем as_ref
    // let value = ref_var.and_then(|v|Some(v.0)); ERROR:cannot move out of borrowed content
    let value = ref_var.as_ref().and_then(|v|Some(v.0)).map(|v|{v+1});
    assert_eq!(Some(9),value);

    let x: Result = Ok(2);
    assert_eq!(x.as_ref(), Ok(&2));

    let x: Result = Err("Error");
    assert_eq!(x.as_ref(), Err(&"Error"));
}


fn main(){
    fn mutate(r: &mut Result) {
        match r.as_mut() {
            Ok(v) => *v = 42,
            Err(e) => *e = 0,
        }
    }

    let mut x: Result = Ok(2);
    mutate(&mut x);
    assert_eq!(x.unwrap(), 42);

    let mut x: Result = Err(13);
    mutate(&mut x);
    assert_eq!(x.unwrap_err(), 0);
}

Применение map

enum.Result.html#method.map

fn main(){
    match exec_ctx.logout_user() {
        Ok(_) => Ok(true),
        Err(_e) => Err(CustomError::BadRequest("User not logged in".to_string())), ❌
    }

    exec_ctx.logout_user()
        .map(|_| true)
        .map_err(|_| CustomError::BadRequest("User not logged in".to_string())) ✅ 
}

fn main(){
    match &self.id {
        Some(value) => Some(value.clone()),❌
        None => None,
    }
    self.id.map(Clone::clone) ✅
}

fn main(){
    match exec_ctx.update_user(Box::new(update_user)) {
        Ok(user) => Ok(Userjuniper::from(user)), ❌
        Err(e) => Err(e),
    }

    exec_ctx.update_user(Box::new(update_user)).map(Userjuniper::from) ✅ 
}

    fn birth_date(&self) -> Option<String> {
       /* match &self.birth_date {
            Some(value) => Some(value.clone()), ❌
            None => None,
       }*/
       self.birth_date.clone().map(std::string::String) ✅ 
    }

#![allow(unused)]

fn main() {
    fn birth_date(&self) -> Option<&str> {
        /* match &self.birth_date {
           Some(v) => Some(v.as_str()), ❌
           None => None,
        }*/
        self.birth_date.as_ref().map(std::string::String::as_str)  ✅
    }
}
  • map() - обрабатывает только Ok(T) вариант Result ф-цией |v|->T
  • map_err() - обрабатывать только Err(E) вариант Result ф-цией |v|->T
  • and_then() - применяет ф-цию к значению внутри Ok
  • map_or() - обрабатывает Ok или возвращает значение
  • map_or_else() - обрабатывает Ok или возвращает значение из ф-ции

enum.Result.html#method.map


// простая обработка
fn test_map_easy(var:Result)->Result{
     var
          .map(|numb| numb+1 )
          .map_err(|_| "var == Err")
          .and_then(|numb| Ok(numb + 1) )
          .map(|numb| numb + 1 )
}
fn main(){
    let var:Result = Ok(1);
    assert_eq!(4,test_map_easy(Ok(1)).unwrap());
}


fn main(){
    let x: Result<_, &str> = Ok("foo");
    assert_eq!(x.map_or(42, |v| v.len()), 3);

    let x: Result<&str, _> = Err("bar");
    assert_eq!(x.map_or(42, |v| v.len()), 42);
}


fn main(){
    let k = 21;

    let x : Result<_, &str> = Ok("foo");
    assert_eq!(x.map_or_else(|e| k * 2, |v| v.len()), 3);

    let x : Result<&str, _> = Err("bar");
    assert_eq!(x.map_or_else(|e| k * 2, |v| v.len()), 42);
}
  • map() - обрабатывает только Ok(T) вариант Result ф-цией |v|->T
  • map_err() - обрабатывать только Err(E) вариант Result ф-цией |v|->T
  • and_then() - нужен для разворачивания результата Ok

enum.Result.html#method.map


fn create_user(new_user:Result,param1:bool) -> Result { 
        let var:Option = Some(1);
            var.map_or(Err("CustomError::Unauthorized"), |_| {
                new_user
                    .map(|_| {
                        // db validate
                        if param1 {   
                            println!("param1");
                            return Err("param1 CustomError::Validation");
                        }
                        Ok(())
                    })
                    .map_err(|_| {
                        println!("map_err");
                        "map_err CustomError::Validation"
                    })
                    .and_then( |res| {
                      // так как  сюда прийдет результат из map Ok 
                      // но может содержать Ok(Err) тогда мы развернем результат
                       res
                    })
                     .and_then(move |res| {
                     // тут мы уверенны что результат из map был Ok
                       println!("create_user_db {:?}",res);
                       Ok(1)
                    })
            })
            .and_then(|value| Ok(value))
}
fn main() {
    println!("{:?}",create_user(Ok(1),false));
}

and_then() - вызывает для данных ф-цию или лямбду если результат Ok(T)

📌 hermanradtke.com use and_then, map

fn main(){
    fn sq(x: u32) -> Result<u32, u32> { Ok(x * x) }
    assert_eq!(Ok(2).and_then(sq).and_then(sq), Ok(16));
}

fn multiply(x: &str, y: &str) -> Result<i32, ParseIntError> {
    //  или Result.Ok<T> или Result.Err<E>
    match x.parse::<i32>() {
        Ok( first )  => {
            match y.parse::<i32>() {
                Ok( second )  => {
                    Ok(first  * second )
                },
                Err(e) => Err(e),
            }
        },
        Err(e) => Err(e),
    }
}
// или более понятный синтаксис с and_then()
fn multiply(x: &str, y: &str) -> Result<i32, ParseIntError> {
    // Если вернулся Result.Ok выполняется вложенное действие
    x.parse::<i32>().and_then(|first | {
        y.parse::<i32>().map(|second | first  * second )
    })
}
  • iter() - Возвращает итератор поверх возможного содержащегося значения.
  • iter_mut()

fn main(){
    let x: Result = Ok(7);
    assert_eq!(x.iter().next(), Some(&7));

    let x: Result = Err("nothing!");
    assert_eq!(x.iter().next(), None);
}


fn main(){
    let mut x: Result = Ok(7);
    match x.iter_mut().next() {
        Some(v) => *v = 40,
        None => {},
    }
    assert_eq!(x, Ok(40));

    let mut x: Result = Err("nothing!");
    assert_eq!(x.iter_mut().next(), None);
}
  • ok() - Converts from Result<T, E> to Option<T>
  • err() - Converts from Result<T, E> to Option<E>
  • transpose(self) -> Option<Result<T, E>>

fn main(){
    let x: Result = Ok(2);
    assert_eq!(x.ok(), Some(2));

    let x: Result = Err("Nothing here");
    assert_eq!(x.ok(), None);
}


fn main(){
    let x: Result = Ok(2); 
    assert_eq!(x.err(), None);

    let x: Result = Err("Nothing here");
    assert_eq!(x.err(), Some("Nothing here"));
}


fn main(){
    // Транспонирует в Result из  Option
    #[derive(Debug, Eq, PartialEq)]
    struct SomeErr;

    let x: Result, SomeErr> = Ok(Some(5));
    let y: Option> = Some(Ok(5));
    assert_eq!(x.transpose(), y);
}
  • unwrap() - Развертывает результат, получая содержимое Ok.
  • unwrap_or() - Возвращает содержащееся значение или значение по умолчанию. Для простых типов! Всегда выполняется
  • unwrap_or_else() - Возвращает содержащееся значение или значение по умолчанию. Для сложных затратных типов! Выполняется лениво, только если None
  • expect() - Паники, если значение равно Err, с сообщением о панике, включая переданное сообщение, и содержимым Err.
  • unwrap_err() - panic! , если значение равно Ok, с настраиваемым паническим сообщением, предоставленным значением Ok
  • expect_err() - Развертывает результат, получая содержание Err
  • unwrap_or_default() - Возвращает содержащееся значение или значение по умолчанию для этого типа

fn main(){
    let x: Result = Ok(2);
    assert_eq!(x.unwrap(), 2);
    let x: Result = Err("emergency failure");
    x.unwrap(); // panics with emergency failure
}

unwrap_or (по умолчанию: T): эта функция разворачивает Option<T>, возвращая внутреннее значение, если оно существует, или предоставленное значение по умолчанию, если параметр равен None. Это просто и эффективно, когда достаточно простого значения по умолчанию. Однако будьте осторожны! Если значение по умолчанию дорого для вычисления или зависит от побочного эффекта, оно всегда будет выполняться, даже если параметр присутствует.

unwrap_or_else(default_fn: FnOnce() -> T): Вместо того, чтобы напрямую указывать значение по умолчанию, мы передаем замыкание или функцию в unwrap_or_else. Эта функция вызывается, только если для параметра установлено значение None, что позволяет избежать ненужных вычислений, когда значение существует. Это позволяет нам откладывать дорогостоящие операции или операции с побочным эффектом до тех пор, пока они не понадобятся, оптимизируя производительность.

Помните, что разница заключается во времени выполнения и эффективности при работе с побочными эффектами. Используйте unwrap_or, когда резервное значение простое и не имеет побочных эффектов. С другой стороны, выбирайте unwrap_or_else, когда вам нужен контроль над логикой отката, особенно когда это связано с дорогостоящими вычислениями или побочными эффектами.


fn main(){
    let optb = 2;
    let x: Result = Ok(9);
    assert_eq!(x.unwrap_or(optb), 9);

    let x: Result = Err("error");
    assert_eq!(x.unwrap_or(optb), optb);
}


fn main(){
    fn count(x: &str) -> usize { x.len() }

    assert_eq!(Ok(2).unwrap_or_else(count), 2);
    assert_eq!(Err("foo").unwrap_or_else(count), 3);
}


fn main(){
    let x: Result = Err("emergency failure");
    x.expect("Testing expect"); // panics
}


fn main(){
    let x: Result = Ok(2);
    x.unwrap_err(); // panics with `2`Run
    let x: Result = Err("emergency failure");
    assert_eq!(x.unwrap_err(), "emergency failur
}

Паники, если значение равно «ОК», с сообщением о панике, включая переданное сообщение, и содержимым «Ок»


fn main(){
    let x: Result = Ok(10);
    x.expect_err("Testing expect_err"); // panics with `Testing expect_err: 10`
}


fn main(){
    let good_year_from_input = "1909";
    let bad_year_from_input = "190blarg";
    let good_year = good_year_from_input.parse().unwrap_or_default();
    let bad_year = bad_year_from_input.parse().unwrap_or_default();

    assert_eq!(1909, good_year);
    assert_eq!(0, bad_year);
}

filter_map

Игнорировать несостоявшиеся элементы с помощью


fn main() {
    let strings = vec!["tofu", "93", "18"];
    let numbers: Vec<_> = strings
        .into_iter()
        .map(|s| s.parse::())
        .filter_map(Result::ok)
        .collect();
    println!("Results: {:?}", numbers);
}