trainings/Rust_2026/rust-bs-2026-09-14/notebooks/08_error_handling.ipynb

938 lines
41 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"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 '<unnamed>' 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: <unknown>\n",
" 7: <unknown>\n",
" 8: <unknown>\n",
" 9: <unknown>\n",
" 10: <unknown>\n",
" 11: <unknown>\n",
" 12: <unknown>\n",
" 13: <unknown>\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<T, E> {\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<String, ReadFileError> { \n",
" let file: io::Result<String> = 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<T>`. 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<E>`.\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<T, E>` 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<T, E>` to a `Result<U, E>` 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<T, E>` to a `Result<T, F>`."
]
},
{
"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<String, ReadFileError> { \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<T> = std::result::Result<T, ReadFileError>;\n",
"\n",
" #[derive(Debug)]\n",
" pub struct ReadFileError {\n",
" message: String\n",
" }\n",
"\n",
" pub fn read_file(path: &str) -> Result<String> { \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<String> {\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<ParseIntError>` 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<Vec<i32>, 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::<i32>()?;\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<io::Error> for ReadNumbersError {\n",
" fn from(err: io::Error) -> Self {\n",
" ReadNumbersError::Io(err)\n",
" }\n",
"}\n",
"\n",
"impl From<std::num::ParseIntError> 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<Vec<i32>, ReadNumbersError> {\n",
" let mut numbers = Vec::new();\n",
" for line_result in file.lines() {\n",
" let line = line_result?;\n",
" let number = line.parse::<i32>()?;\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<dyn std::error::Error>`"
]
},
{
"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<dyn std::error::Error>;\n",
"type GenericResult<T> = Result<T, GenericError>;"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* and change return type from function to `GenericResult<Vec<i32>>`"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"use std::io::{self, BufRead};\n",
"\n",
"fn parse_numbers(file: &mut dyn BufRead) -> GenericResult<Vec<i32>> {\n",
" let mut numbers = Vec::new();\n",
" for line_result in file.lines() {\n",
" let line = line_result?;\n",
" let number = line.parse::<i32>()?;\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<Person, JsonError> {\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<Vec<i32>, 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::<i32>() {\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<Vec<i32>> {\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::<i32>()\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<Vec<i32>> {\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<u32> {\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
}