{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Error handling" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Panic\n", "\n", "* A program panics when it encounters something so messed up that there must be a bug in the program itself. Something like:\n", " * Out-of-bounds array access\n", " * Integer division by zero\n", " * Calling `.unwrap()` on an `Option` that happens to be `None`\n", " * Assertion failure\n", "* A panic is not a crash. It’s not undefined behavior. The behavior is well-defined!\n", "* Macro `panic!` can trigger a panic directly" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "\n", "thread '' panicked at src\\lib.rs:6:1:\n", "Critical error!\n", "stack backtrace:\n", " 0: std::panicking::begin_panic_handler\n", " at /rustc/1159e78c4747b02ef996e55082b704c09b970588/library\\std\\src\\panicking.rs:697\n", " 1: core::panicking::panic_fmt\n", " at /rustc/1159e78c4747b02ef996e55082b704c09b970588/library\\core\\src\\panicking.rs:75\n", " 2: ctx::run_user_code_2::{{closure}}\n", " 3: _rust_try.llvm.7135721178753807917\n", " 4: std::panic::catch_unwind\n", " 5: run_user_code_2\n", " 6: \n", " 7: \n", " 8: \n", " 9: \n", " 10: \n", " 11: \n", " 12: \n", " 13: \n", " 14: BaseThreadInitThunk\n", " 15: RtlUserThreadStart\n", "note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.\n" ] } ], "source": [ "panic!(\"Critical error!\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Unwinding\n", "\n", "* Panic typically proceeds as follows:\n", " * An error message is printed to the terminal. If you set the `RUST_BACKTRACE` environment variable, as the messages suggests, Rust will also dump the stack at this point.\n", " * The stack is unwound. This is a lot like C++ exception handling. Any temporary values, local variables, or arguments that the current function was using are dropped, in the reverse of the order they were created. Dropping a value simply means cleaning up after it: any `String`s or `Vec`s the program was using are freed, any open `File`s are closed, and so on. User-defined `drop` methods are called too.\n", " * Finally, the thread exits. If the panicking thread was the main thread, then the whole process exits (with a nonzero exit code)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Aborting\n", "\n", "* Stack unwinding is the default panic behavior, but there are two circumstances in which Rust does not try to unwind the stack.\n", " * If a `.drop()` method triggers a second panic while Rust is still trying to clean up after the first, this is considered fatal. Rust stops unwinding and aborts the whole process.\n", " * Rust’s panic behavior is customizable. If you compile with `-C panic=abort`, the first panic in your program immediately aborts the process. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* A `Result` represents an operation that can either succeed (returning a result) or fail (returning an error).\n", "* It is defined as:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "mod explain {\n", " pub enum Result {\n", " Ok(T),\n", " Err(E),\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "#[derive(Debug)]\n", "struct ReadFileError {\n", " message: String\n", "}\n", "\n", "impl std::fmt::Display for ReadFileError {\n", " fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n", " write!(f, \"ReadFileError: {}\", self.message)\n", " }\n", "}\n", "\n", "impl std::error::Error for ReadFileError {}" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "ename": "Error", "evalue": "failed to resolve: use of unresolved module or unlinked crate `io`", "output_type": "error", "traceback": [ "\u001b[31m[E0433] Error:\u001b[0m failed to resolve: use of unresolved module or unlinked crate `io`", " \u001b[38;5;246m╭\u001b[0m\u001b[38;5;246m─\u001b[0m\u001b[38;5;246m[\u001b[0m command_5:1:1 \u001b[38;5;246m]\u001b[0m", " \u001b[38;5;246m│\u001b[0m", " \u001b[38;5;246m2 │\u001b[0m \u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mf\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m:\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;54mi\u001b[0m\u001b[38;5;54mo\u001b[0m\u001b[38;5;249m:\u001b[0m\u001b[38;5;249m:\u001b[0m\u001b[38;5;249mR\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249mu\u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m<\u001b[0m\u001b[38;5;249mS\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mg\u001b[0m\u001b[38;5;249m>\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m=\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249md\u001b[0m\u001b[38;5;249m:\u001b[0m\u001b[38;5;249m:\u001b[0m\u001b[38;5;249mf\u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249m:\u001b[0m\u001b[38;5;249m:\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249md\u001b[0m\u001b[38;5;249m_\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249m_\u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mg\u001b[0m\u001b[38;5;249m(\u001b[0m\u001b[38;5;249mp\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249mh\u001b[0m\u001b[38;5;249m)\u001b[0m\u001b[38;5;249m;\u001b[0m", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;54m─\u001b[0m\u001b[38;5;54m┬\u001b[0m ", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;54m╰\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m use of unresolved module or unlinked crate `io`", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;100m│\u001b[0m ", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;100m╰\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m help: a builtin type with a similar name exists: `i8`", " \u001b[38;5;240m │\u001b[0m ", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;115mNote\u001b[0m: help: consider importing this module: `use std::io;", " \u001b[38;5;240m │\u001b[0m ", " \u001b[38;5;240m │\u001b[0m `", "\u001b[38;5;246m───╯\u001b[0m" ] } ], "source": [ "fn read_file(path: &str) -> Result { \n", " let file: io::Result = std::fs::read_to_string(path);\n", " \n", " match file {\n", " Ok(content) => Result::Ok(content),\n", " Err(err) => Result::Err(ReadFileError{message: err.to_string()}),\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "ename": "Error", "evalue": "cannot find function `read_file` in this scope", "output_type": "error", "traceback": [ "\u001b[31m[E0425] Error:\u001b[0m cannot find function `read_file` in this scope", " \u001b[38;5;246m╭\u001b[0m\u001b[38;5;246m─\u001b[0m\u001b[38;5;246m[\u001b[0m command_6:1:1 \u001b[38;5;246m]\u001b[0m", " \u001b[38;5;246m│\u001b[0m", " \u001b[38;5;246m1 │\u001b[0m \u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mf\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m_\u001b[0m\u001b[38;5;249mc\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m=\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;54mr\u001b[0m\u001b[38;5;54me\u001b[0m\u001b[38;5;54ma\u001b[0m\u001b[38;5;54md\u001b[0m\u001b[38;5;54m_\u001b[0m\u001b[38;5;54mf\u001b[0m\u001b[38;5;54mi\u001b[0m\u001b[38;5;54ml\u001b[0m\u001b[38;5;54me\u001b[0m\u001b[38;5;249m(\u001b[0m\u001b[38;5;249m\"\u001b[0m\u001b[38;5;249mf\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m.\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249mx\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m\"\u001b[0m\u001b[38;5;249m)\u001b[0m\u001b[38;5;249m;\u001b[0m", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m┬\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m ", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;54m╰\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m not found in this scope", "\u001b[38;5;246m───╯\u001b[0m" ] } ], "source": [ "let file_content = read_file(\"file.txt\");\n", "\n", "match file_content {\n", " Ok(content) => println!(\"{}\", content),\n", " Err(err) => println!(\"{}\", err),\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Result methods" ] }, { "cell_type": "markdown", "metadata": { "vscode": { "languageId": "rust" } }, "source": [ "* `result.unwrap()` also returns the success value, if result is a success result. However, if result is an error result, this method panics.\n", "* `result.expect(message)` is the same as `.unwrap()`, but lets you provide a message that it prints in case of panic.\n", "* `result.is_ok()` and `result.is_err()` return a `bool` telling if result is a success result or an error result.\n", "* `result.ok()` returns the success value, if any, as an `Option`. If result is a success result, this returns `Some(success_value)`; otherwise, it returns `None`, discarding the error value.\n", "* `result.err()` returns the error value, if any, as an `Option`.\n", "* `result.unwrap_or(fallback)` returns the success value, if result is a success result. Otherwise, it returns `fallback`, discarding the error value." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "ename": "Error", "evalue": "cannot find function `read_file` in this scope", "output_type": "error", "traceback": [ "\u001b[31m[E0425] Error:\u001b[0m cannot find function `read_file` in this scope", " \u001b[38;5;246m╭\u001b[0m\u001b[38;5;246m─\u001b[0m\u001b[38;5;246m[\u001b[0m command_7:1:1 \u001b[38;5;246m]\u001b[0m", " \u001b[38;5;246m│\u001b[0m", " \u001b[38;5;246m3 │\u001b[0m \u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mf\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m_\u001b[0m\u001b[38;5;249mc\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m=\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;54mr\u001b[0m\u001b[38;5;54me\u001b[0m\u001b[38;5;54ma\u001b[0m\u001b[38;5;54md\u001b[0m\u001b[38;5;54m_\u001b[0m\u001b[38;5;54mf\u001b[0m\u001b[38;5;54mi\u001b[0m\u001b[38;5;54ml\u001b[0m\u001b[38;5;54me\u001b[0m\u001b[38;5;249m(\u001b[0m\u001b[38;5;249m\"\u001b[0m\u001b[38;5;249mu\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mk\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mw\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249m_\u001b[0m\u001b[38;5;249mf\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m.\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249mx\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m\"\u001b[0m\u001b[38;5;249m)\u001b[0m\u001b[38;5;249m.\u001b[0m\u001b[38;5;249mu\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mw\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249mp\u001b[0m\u001b[38;5;249m_\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249m(\u001b[0m\u001b[38;5;249mD\u001b[0m\u001b[38;5;249mE\u001b[0m\u001b[38;5;249mF\u001b[0m\u001b[38;5;249mA\u001b[0m\u001b[38;5;249mU\u001b[0m\u001b[38;5;249mL\u001b[0m\u001b[38;5;249mT\u001b[0m\u001b[38;5;249m_\u001b[0m\u001b[38;5;249mF\u001b[0m\u001b[38;5;249mI\u001b[0m\u001b[38;5;249mL\u001b[0m\u001b[38;5;249mE\u001b[0m\u001b[38;5;249m_\u001b[0m\u001b[38;5;249mC\u001b[0m\u001b[38;5;249mO\u001b[0m\u001b[38;5;249mN\u001b[0m\u001b[38;5;249mT\u001b[0m\u001b[38;5;249mE\u001b[0m\u001b[38;5;249mN\u001b[0m\u001b[38;5;249mT\u001b[0m\u001b[38;5;249m.\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249m_\u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mg\u001b[0m\u001b[38;5;249m(\u001b[0m\u001b[38;5;249m)\u001b[0m\u001b[38;5;249m)\u001b[0m\u001b[38;5;249m;\u001b[0m", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m┬\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m ", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;54m╰\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m not found in this scope", "\u001b[38;5;246m───╯\u001b[0m" ] } ], "source": [ "static DEFAULT_FILE_CONTENT: &str = \"Default content\";\n", "\n", "let file_content = read_file(\"unknown_file.txt\").unwrap_or(DEFAULT_FILE_CONTENT.to_string());\n", "\n", "file_content" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* `result.unwrap_or_else(fallback_fn)` is the same, but instead of passing a fallback value directly, you pass a function or closure. This is for cases where it would be wasteful to compute a fallback value if you’re not going to use it. The `fallback_fn` is called only if we have an error result.\n", "* `result.as_ref()` converts a `Result` to a `Result<&T, &E>`, borrowing a reference to the success or error value in the existing result.\n", "* `result.as_mut()` is the same, but borrows a mutable reference. The return type is `Result<&mut T, &mut E>`.\n", "\n", "#### Mapping\n", "\n", "* Mapping means transforming the success or error value inside a `Result`, while leaving the other value untouched. It simplifies code that would otherwise require matching on the `Result` and handling both cases.\n", " * `result.map(success_fn)` transforms a `Result` to a `Result` by applying the function `success_fn` to the success value, if any. The error value is left untouched.\n", " * `result.map_err(error_fn)` is the same, but applies `error_fn` to the error value, if any, transforming a `Result` to a `Result`." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This is a file with some text.\n" ] }, { "data": { "text/plain": [ "()" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fn read_file(path: &str) -> Result { \n", " std::fs::read_to_string(path).map_err(|err| ReadFileError{message: err.to_string()})\n", "}\n", "\n", "let file_content = read_file(\"file.txt\");\n", "\n", "match file_content {\n", " Ok(content) => println!(\"{}\", content),\n", " Err(err) => println!(\"{}\", err),\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Result Type Aliases\n", "\n", "* Modules often define a `Result` type alias to avoid having to repeat an error type that’s used consistently by almost every function in the module" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Some(\"This is a file with some text.\")" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mod custom {\n", " pub type Result = std::result::Result;\n", "\n", " #[derive(Debug)]\n", " pub struct ReadFileError {\n", " message: String\n", " }\n", "\n", " pub fn read_file(path: &str) -> Result { \n", " let file = std::fs::read_to_string(path);\n", " \n", " match file {\n", " Ok(content) => Result::Ok(content),\n", " Err(err) => Result::Err(ReadFileError{message: err.to_string()}),\n", " }\n", " }\n", "}\n", "\n", "custom::read_file(\"file.txt\").ok()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Trait std::error::Error - Printing Errors" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* All of standard library errors have a common interface, the `std::error::Error` trait, which means they share the following features:\n", " * They’re all printable using `println!()`. Printing an error with the `{}` format specifier typically displays only a brief error message. Alternatively, you can print with the `{:?}` format specifier, to get a `Debug` view of the error\n", " * `err.description()` returns an error message as a `&str`.\n", " * `err.source()` returns an `Option<&(dyn Error + 'static)>`: the underlying error, if any, that triggered `err`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Propagating Error" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* In most places where we try something that could fail, we don’t want to catch and handle the error immediately\n", "* Instead, if an error occurs, we usually want to let our caller deal with it. We want errors to propagate up the call stack.\n", "* Rust has a `?` operator that does this. You can add a `?` to any expression that produces a `Result`, such as the result of a function call:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "use std::io;\n", "\n", "fn read_file_content(name: &str) -> io::Result {\n", " use std::fs::File;\n", " use std::io::prelude::*;\n", " \n", " let mut file = File::open(name)?; \n", " let mut contents = String::new();\n", " file.read_to_string(&mut contents)?;\n", " Ok(contents)\n", "}" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Error: Os { code: 2, kind: NotFound, message: \"Nie można odnaleźć określonego pliku.\" }\n" ] }, { "data": { "text/plain": [ "()" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let file_content = read_file_content(\"unknown_file.txt\");\n", "\n", "match file_content {\n", " Ok(content) => println!(\"{}\", content),\n", " Err(err) => println!(\"Error: {:?}\", err),\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* The behavior of `?` depends on whether this function returns a success result or an error result:\n", " * on success, it unwraps the Result to get the success value inside.\n", " * on error, it immediately returns from the enclosing function, passing the error result up the call chain. To ensure that this works, `?` can only be used in functions that have a `Result` return type." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Working with Multiple Error Types" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "ename": "Error", "evalue": "`?` couldn't convert the error to `std::io::Error`", "output_type": "error", "traceback": [ "\u001b[31m[E0277] Error:\u001b[0m `?` couldn't convert the error to `std::io::Error`", " \u001b[38;5;246m╭\u001b[0m\u001b[38;5;246m─\u001b[0m\u001b[38;5;246m[\u001b[0m command_14:1:1 \u001b[38;5;246m]\u001b[0m", " \u001b[38;5;246m│\u001b[0m", " \u001b[38;5;246m3 │\u001b[0m \u001b[38;5;249mf\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249md\u001b[0m\u001b[38;5;249m_\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mu\u001b[0m\u001b[38;5;249mm\u001b[0m\u001b[38;5;249mb\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249m(\u001b[0m\u001b[38;5;249mf\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m:\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m&\u001b[0m\u001b[38;5;249mm\u001b[0m\u001b[38;5;249mu\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249md\u001b[0m\u001b[38;5;249my\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mB\u001b[0m\u001b[38;5;249mu\u001b[0m\u001b[38;5;249mf\u001b[0m\u001b[38;5;249mR\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249md\u001b[0m\u001b[38;5;249m)\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m-\u001b[0m\u001b[38;5;249m>\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;100mR\u001b[0m\u001b[38;5;100me\u001b[0m\u001b[38;5;100ms\u001b[0m\u001b[38;5;100mu\u001b[0m\u001b[38;5;100ml\u001b[0m\u001b[38;5;100mt\u001b[0m\u001b[38;5;100m<\u001b[0m\u001b[38;5;100mV\u001b[0m\u001b[38;5;100me\u001b[0m\u001b[38;5;100mc\u001b[0m\u001b[38;5;100m<\u001b[0m\u001b[38;5;100mi\u001b[0m\u001b[38;5;100m3\u001b[0m\u001b[38;5;100m2\u001b[0m\u001b[38;5;100m>\u001b[0m\u001b[38;5;100m,\u001b[0m\u001b[38;5;100m \u001b[0m\u001b[38;5;100mi\u001b[0m\u001b[38;5;100mo\u001b[0m\u001b[38;5;100m:\u001b[0m\u001b[38;5;100m:\u001b[0m\u001b[38;5;100mE\u001b[0m\u001b[38;5;100mr\u001b[0m\u001b[38;5;100mr\u001b[0m\u001b[38;5;100mo\u001b[0m\u001b[38;5;100mr\u001b[0m\u001b[38;5;100m>\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m{\u001b[0m", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m┬\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m ", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;100m╰\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m expected `std::io::Error` because of this", " \u001b[38;5;240m │\u001b[0m ", " \u001b[38;5;246m7 │\u001b[0m \u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mu\u001b[0m\u001b[38;5;249mm\u001b[0m\u001b[38;5;249mb\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m=\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m.\u001b[0m\u001b[38;5;54mp\u001b[0m\u001b[38;5;54ma\u001b[0m\u001b[38;5;54mr\u001b[0m\u001b[38;5;54ms\u001b[0m\u001b[38;5;54me\u001b[0m\u001b[38;5;54m:\u001b[0m\u001b[38;5;54m:\u001b[0m\u001b[38;5;54m<\u001b[0m\u001b[38;5;54mi\u001b[0m\u001b[38;5;54m3\u001b[0m\u001b[38;5;54m2\u001b[0m\u001b[38;5;54m>\u001b[0m\u001b[38;5;54m(\u001b[0m\u001b[38;5;54m)\u001b[0m\u001b[38;5;68m?\u001b[0m\u001b[38;5;249m;\u001b[0m", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m┬\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;68m┬\u001b[0m ", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;54m╰\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m this can't be annotated with `?` because it has type `Result<_, ParseIntError>`", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;68m│\u001b[0m ", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;68m╰\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m the trait `From` is not implemented for `std::io::Error`", "\u001b[38;5;246m───╯\u001b[0m" ] } ], "source": [ "use std::io::{self, BufRead};\n", "\n", "fn read_numbers(file: &mut dyn BufRead) -> Result, io::Error> {\n", " let mut numbers = Vec::new();\n", " for line_result in file.lines() {\n", " let line = line_result?;\n", " let number = line.parse::()?;\n", " numbers.push(number);\n", " }\n", " Ok(numbers)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Case 1 - Trait `From`" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "use std::io::BufRead;\n", "\n", "#[derive(Debug)]\n", "enum ReadNumbersError {\n", " Io(io::Error),\n", " ParseInt(std::num::ParseIntError),\n", "}\n", "\n", "impl From for ReadNumbersError {\n", " fn from(err: io::Error) -> Self {\n", " ReadNumbersError::Io(err)\n", " }\n", "}\n", "\n", "impl From for ReadNumbersError {\n", " fn from(err: std::num::ParseIntError) -> Self {\n", " ReadNumbersError::ParseInt(err)\n", " }\n", "}\n", "\n", "fn read_numbers(file: &mut dyn BufRead) -> Result, ReadNumbersError> {\n", " let mut numbers = Vec::new();\n", " for line_result in file.lines() {\n", " let line = line_result?;\n", " let number = line.parse::()?;\n", " numbers.push(number);\n", " }\n", " Ok(numbers)\n", "}" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 2, 3, 42, 665, 6, 7, 8]\n" ] } ], "source": [ "use std::io::{self, BufReader};\n", "\n", "fn main() {\n", " let file = std::fs::File::open(\"numbers.txt\").unwrap();\n", "\n", " let mut buffer_reader = BufReader::new(file);\n", "\n", " let numbers_result = read_numbers(&mut buffer_reader);\n", "\n", " match numbers_result {\n", " Ok(numbers) => println!(\"{:?}\", numbers),\n", " Err(e) => println!(\"Error reading numbers: {:?}\", e),\n", " }\n", "}\n", "\n", "main();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Case 2 - `Box`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Code does not compile - there is no conversion from `ParseIntError` to `io::Error`\n", "* The easiest way to handle multiple error types is to define these type aliases:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "type GenericError = Box;\n", "type GenericResult = Result;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* and change return type from function to `GenericResult>`" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "use std::io::{self, BufRead};\n", "\n", "fn parse_numbers(file: &mut dyn BufRead) -> GenericResult> {\n", " let mut numbers = Vec::new();\n", " for line_result in file.lines() {\n", " let line = line_result?;\n", " let number = line.parse::()?;\n", " numbers.push(number);\n", " }\n", " Ok(numbers)\n", "}" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Error reading numbers: ParseIntError { kind: InvalidDigit }\n" ] } ], "source": [ "use std::io::{self, BufReader};\n", "\n", "fn main() {\n", " let file = std::fs::File::open(\"invalid_numbers.txt\").unwrap();\n", " let mut buffer_reader = BufReader::new(file);\n", " \n", " let numbers_result = parse_numbers(&mut buffer_reader);\n", "\n", " match numbers_result {\n", " Ok(numbers) => println!(\"{:?}\", numbers),\n", " Err(e) => println!(\"Error reading numbers: {:?}\", e),\n", " }\n", "}\n", "main();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Declaring a Custom Error Types" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "#[derive(Debug, Clone)]\n", "pub struct JsonError {\n", " pub message: String,\n", " pub line: usize,\n", " pub column: usize,\n", "}" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "use std;\n", "use std::fmt;\n", "\n", "// Errors should be printable.\n", "impl fmt::Display for JsonError {\n", " fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {\n", " write!(f, \"{} ({}:{})\", self.message, self.line, self.column)\n", " }\n", "}\n", "\n", "// Errors should implement the std::error::Error trait.\n", "impl std::error::Error for JsonError {\n", " fn description(&self) -> &str {\n", " &self.message\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ ":dep serde_json = \"1.0.64\"\n", ":dep serde = { version=\"1.0.127\", features=[\"derive\"] }\n", "\n", "use serde::{Deserialize, Serialize};\n", "\n", "#[derive(Serialize, Deserialize, Debug)]\n", "struct Person {\n", " name: String,\n", " age: i32,\n", "}\n", "\n", "fn load_from_json() -> Result {\n", " let json = r#\"\n", " {\n", " \"name\": \"John\",\n", " \"age\": 30\n", " }\n", " \"#;\n", "\n", " let person: Person = match serde_json::from_str(json) {\n", " Ok(data) => data,\n", " Err(err) => {\n", " let line = err.line();\n", " let column = err.column();\n", " let message = err.to_string();\n", " return Err(JsonError { message, line, column });\n", " }\n", " };\n", "\n", " Ok(person)\n", "}" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "None" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let person = load_from_json().err();\n", "person" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Crates for Error Handling" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## thiserror\n", "\n", "* The `thiserror` crate provides a convenient way to define custom error types in Rust by using procedural macros. It simplifies the process of implementing the `std::error::Error` trait and provides useful features for error handling.\n", "* With `thiserror`, you can define your error types as enums or structs and annotate them with the `#[derive(Error)]` attribute. This automatically generates the necessary implementations for the `Error` trait, including methods like `source()` and `description()`.\n", "* The crate also allows you to specify error messages using the `#[error(\"...\")]` attribute, which supports formatting and interpolation of fields within the error type.\n", "* Additionally, `thiserror` supports automatic conversion from other error types using the `#[from]` attribute, making it easy to wrap and propagate errors from different sources." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Error parsing numbers: Failed to parse number: 66d5\n" ] } ], "source": [ ":dep thiserror = \"2.0\"\n", "\n", "mod thiserror_example {\n", " use thiserror::Error;\n", "\n", " #[derive(Error, Debug)]\n", " pub enum ParseNumberError {\n", " #[error(\"File not found\")]\n", " NotFound(#[from] std::io::Error), // automatic conversion from std::io::Error\n", " #[error(\"Failed to parse number: {0}\")]\n", " ParseError(String),\n", " }\n", "\n", " pub fn parse_numbers(path: &str) -> Result, ParseNumberError> {\n", " use std::fs::File;\n", " use std::io::{self, BufRead, BufReader};\n", "\n", " let file = File::open(path)?;\n", " let reader = BufReader::new(file);\n", " let mut numbers = Vec::new();\n", "\n", " for line_result in reader.lines() {\n", " let line = line_result?;\n", " match line.parse::() {\n", " Ok(num) => numbers.push(num),\n", " Err(_) => return Err(ParseNumberError::ParseError(line)),\n", " }\n", " }\n", "\n", " Ok(numbers)\n", " }\n", "}\n", "\n", "fn main() {\n", " match thiserror_example::parse_numbers(\"invalid_numbers.txt\") {\n", " Ok(numbers) => println!(\"Parsed numbers: {:?}\", numbers),\n", " Err(e) => println!(\"Error parsing numbers: {}\", e),\n", " }\n", "}\n", "\n", "main();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## anyhow\n", "\n", "* The `anyhow` crate provides a simple and flexible way to handle errors in Rust applications. It allows you to create and propagate errors without having to define custom error types for every possible error scenario.\n", "* With `anyhow`, you can use the `anyhow::Error` type to represent errors, which can encapsulate any error type that implements the `std::error::Error` trait. This makes it easy to work with different error types without having to define a separate error enum or struct for each case.\n", "* The crate also provides convenient macros like `anyhow!` for creating errors with formatted messages, and the `Context` trait for adding additional context to errors as they propagate through your code.\n", "* Library convinent when prototyping or building applications where you want to focus on error handling without the overhead of defining numerous custom error types." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 2, 3, 42, 665, 6, 7, 8]\n" ] } ], "source": [ ":dep anyhow = \"1.0\"\n", "\n", "mod anyhow_example {\n", " use anyhow::Context;\n", " use std::io::{BufRead, BufReader};\n", "\n", " pub fn parse_numbers(file: &mut dyn BufRead) -> anyhow::Result> {\n", " let mut numbers = Vec::new();\n", " for (index, line_result) in file.lines().enumerate() {\n", " let line = line_result.context(format!(\"Failed to read line {}\", index + 1))?;\n", " let number = line.parse::()\n", " .context(format!(\"Failed to parse '{}' as i32 on line {}\", line, index + 1))?;\n", " numbers.push(number);\n", " }\n", " Ok(numbers)\n", " }\n", "\n", " pub fn read_numbers(path: &str) -> anyhow::Result> {\n", " let file = std::fs::File::open(path)\n", " .with_context(|| format!(\"Failed to open file '{}'\", path))?;\n", " \n", " let mut buffer_reader = BufReader::new(file);\n", " let numbers = parse_numbers(&mut buffer_reader)\n", " .with_context(|| format!(\"Failed to read numbers from file '{}'\", path))?;\n", " \n", " Ok(numbers)\n", " }\n", "}\n", "\n", "fn main() {\n", " let result = anyhow_example::read_numbers(\"numbers.txt\"); \n", " match result {\n", " Ok(numbers) => println!(\"{:?}\", numbers),\n", " Err(err) => println!(\"Error: {:?}\", err),\n", " }\n", "}\n", "\n", "main();" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "use anyhow::{anyhow, Result};\n", "\n", "fn get_user_age(name: &str) -> Result {\n", " match name {\n", " \"Alice\" => Ok(30),\n", " \"Bob\" => Ok(25),\n", " _ => Err(anyhow!(\"No age data available for user '{}'\", name)),\n", " }\n", "}\n", "\n", "fn main() -> Result<()> {\n", " let age = get_user_age(\"Charlie\")?;\n", " println!(\"User age: {}\", age);\n", " Ok(())\n", "}\n", "\n", "main();" ] } ], "metadata": { "kernelspec": { "display_name": "Rust", "language": "rust", "name": "rust" }, "language_info": { "codemirror_mode": "rust", "file_extension": ".rs", "mimetype": "text/rust", "name": "rust", "pygment_lexer": "rust", "version": "" } }, "nbformat": 4, "nbformat_minor": 2 }