From 6f0fa900c37b05f2da044a59787829161b68ac46 Mon Sep 17 00:00:00 2001 From: ElnuDev Date: Wed, 28 Sep 2022 14:59:08 -0700 Subject: [PATCH] Add old edits on draft from August 4 --- ...ndling-multiple-possible-errors-in-rust.md | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 content/posts/handling-multiple-possible-errors-in-rust.md diff --git a/content/posts/handling-multiple-possible-errors-in-rust.md b/content/posts/handling-multiple-possible-errors-in-rust.md new file mode 100644 index 0000000..ed46844 --- /dev/null +++ b/content/posts/handling-multiple-possible-errors-in-rust.md @@ -0,0 +1,81 @@ +--- +title: "Handling Multiple Possible Errors in Rust" +date: 2022-08-04 +tags: + - programming +draft: true +--- + +## Background + +Rust handles errors better than any other language I’ve ever used. All possible errors are known at build time, and by design Rust forces you to at a bare minimum *acknowledge* that these errors are possible with `.unwrap()`, otherwise writing specific error handling logic. + +### The `Result` enum + +All of this is thanks to the `Result` enum, which is defined as follows: + +```RS +enum Result { + Ok(T), + Err(E), +} +``` + +`Result` is a generic type that requires two type parameters, `T`, the actual value the result is wrapping, and `E`, the error type in case something goes wrong. In the success case, the `Ok(T)` variant will be used, and in an error case, `Err(E)` will be used. + +What makes this powerful is the fact that since `Result` wraps the actual result value, access to that value is prevented unless the possibility of an error is acknowledged. + +### Handling `Result` + +For the sake of example, say we’re writing a theoretical function, `acquire_banana`, that drives to the store, purchases a banana, and returns the banana. + +We have a function called `drive_to_store`. It returns a `Result` that can either be nothing `()`, or a `NavigationError`. + +```RS +fn drive_to_store() -> Result<(), NavigationError> { ... } +``` + +We then have the following function, `acquire_banana`. It also returns a `Result`, but this time it can either be a `Banana` or a `PurchaseError`. + +```RS +fn purchase_banana() -> Result { ... } +``` + +In other languages, we might write the function something like this: + +```RS +fn acquire_banana() -> Banana { + drive_to_store(); + purchase_banana() +} +``` + +However, this doesn’t work in Rust. There are two issues here. First, while `drive_to_store();` is in theory valid code (it returns an enum that we ignore), `Result`s **must** be handled. Otherwise, you will get compile warnings. Second, we cannot return `purchase_banana` from the function, because its return type is `Result`, not `Banana`. + +The naïve solution to this would be using the `unwrap` method on the `Result`s, like this: + +```RS +fn acquire_banana() -> Banana { + drive_to_store().unwrap(); + purchase_banana().unwrap() +} +``` + +The `.unwrap()` method does exactly what it sounds like. If a `Result` is the `Ok(T)` variant, it returns `T`. However, the naïvety here lies in what happens on the `Err(E)` variant. In that case, it *panics*, printing the error and kills the thread/program. A well-designed program should take errors into account, so this solution is unacceptable. + +The best solution is in this case is **error propagation** using the question mark `?` operator. It works similarly to `.unwrap()`, but instead of panicking on the `Err(E)` case, it returns `Err(E)` from the function. This lets you quickly unwrap results without convoluted `match` statements. + +This lets you convert this: + +```RS +let x = match get_result() { + Ok(value) => value, + Err(error) => return Err(error), +}; +``` + +Into this: + +```RS +let x = get_result()?; +```