It’s worse than that. In C++, if you fail to catch an exception, then std::terminate() is called. In Rust the only options are roughly equivalent to C++ noexcept, or std::terminate() [panic in Rust]. There’s nothing in between.
i’ve been meaning to get into rust; but i’m learning that i lack motivation if it’s not part of my job and becoming a sysadmin again doesn’t require it.
In some cases unwrap could be useful if you don’t care about a panic or if you know the value can’t be None. Sometimes it’s just used as a shortcut. You can likewise do this in C#:
A panic isn’t the same as an exception though, you can’t ‘catch’ a panic, it’s unrecoverable and the program will terminate more-or-less immediately.
Rust provides a generic type Result<T, E>, T being a successful result and E being some error type, which you are encouraged to use along with pattern matching to make sure all cases are handled.
Rust does not have exceptions. You never have to try/catch. Functions usually encode the possible failures in their types, so you’d have something like this C++ snippet:
And then the caller of downloadFile has to decide what to do if it returns an error:
auto res = std::visit(overloaded {
[](FileDownloadError e) { /* ignore error, or handle it somehow, and return a new path */ },
[](std::filesystem::path p) { return p; }
}, downloadFile(url));
/* res is guaranteed to be a valid std::filesystem::path, you don't have to care about errors from downloadFile anymore */
However, Rust makes this whole thing a lot easier, by providing syntax sugar, and there are helper libraries that reduce the boilerplate. You usually end up with something like
(notice the #[from], which forwards the error message etc from the std::io::Error type)
The Result type is kind of like a std::variant with two template arguments, and, mostly by convention, the first one denotes the successful execution, while the second one is the error type. Result has a bunch of methods defined on it that help you with error handling.
Consumer code is something like this:
letres : Path = download_file(url).unwrap_or_else(|e| {
/* Ignore the error or handle it. You have to return a new path here */
});
/* res is guaranteed to be a valid Path, you don't have to care about errors from download_file anymore */
Or
letres : Path = download_file(url)?;
/* res is guaranteed to be a valid Path, you don't have to care about errors from download_file anymore */
Which will just forward the error to your caller (but your function has to return Result as well), or proceed with the execution if the function succeeded.
Finally, download_file(url).unwrap() is if you can neither ignore, nor handle, nor pass the error to the caller. It will abort if the function fails in any way, and there’s no (practical) way to catch that abort.
so you have to wrap everything in a try/catch?
It’s worse than that. In C++, if you fail to catch an exception, then std::terminate() is called. In Rust the only options are roughly equivalent to C++ noexcept, or std::terminate() [panic in Rust]. There’s nothing in between.
i’ve been meaning to get into rust; but i’m learning that i lack motivation if it’s not part of my job and becoming a sysadmin again doesn’t require it.
Not really, because rust doesn’t have exceptions. Instead you are encouraged to handle every possible case with pattern matching. For example:
fn maybe_add_one(number: Option<u8>) -> u8 { match number { None => 0, Some(i) => i + 1, } }Option<u8> is a type which can either be some 8bit unsigned integer, or none. It’s conceptually similar to a
Nullable<int>in C#.In C# you could correctly implement this like:
public int MaybeAddOne(int? number) { if (number.HasValue) { return number.Value + 1; } return 0; }In rust, you can call Unwrap on an option to get the underlying value, but it will panic if the value is None (because None isn’t a u8):
fn maybe_add_one(number: Option<u8>) -> u8 { number.unwrap() + 1 }In some cases unwrap could be useful if you don’t care about a panic or if you know the value can’t be
None. Sometimes it’s just used as a shortcut. You can likewise do this in C#:public int MaybeAddOne(int? number) { return number.Value + 1; }But this throws an exception if number is null.
A panic isn’t the same as an exception though, you can’t ‘catch’ a panic, it’s unrecoverable and the program will terminate more-or-less immediately.
Rust provides a generic type
Result<T, E>, T being a successful result and E being some error type, which you are encouraged to use along with pattern matching to make sure all cases are handled.Rust does not have exceptions. You never have to try/catch. Functions usually encode the possible failures in their types, so you’d have something like this C++ snippet:
struct HttpError { std::string message; int responseCode; } struct FsError { std::string message; } typedef std::variant<HttpError, IoError> FileDownloadError; std::variant<std::filesystem::path, FileDownloadError> downloadFile(std::string url) { /* ... */ }And then the caller of
downloadFilehas to decide what to do if it returns an error:auto res = std::visit(overloaded { [](FileDownloadError e) { /* ignore error, or handle it somehow, and return a new path */ }, [](std::filesystem::path p) { return p; } }, downloadFile(url)); /* res is guaranteed to be a valid std::filesystem::path, you don't have to care about errors from downloadFile anymore */However, Rust makes this whole thing a lot easier, by providing syntax sugar, and there are helper libraries that reduce the boilerplate. You usually end up with something like
#[derive(Error, Debug)] enum FileDownloadError { #[error("HTTP request failed")] HttpError(http::status::StatusCode), #[error("Filesystem error: {0}") FSError(#[from] std::io::Error), } fn download_file(String url) -> Result<Path, FileDownloadError> {/* ... */}(notice the
#[from], which forwards the error message etc from thestd::io::Errortype)The
Resulttype is kind of like astd::variantwith two template arguments, and, mostly by convention, the first one denotes the successful execution, while the second one is the error type.Resulthas a bunch of methods defined on it that help you with error handling.Consumer code is something like this:
let res : Path = download_file(url).unwrap_or_else(|e| { /* Ignore the error or handle it. You have to return a new path here */ }); /* res is guaranteed to be a valid Path, you don't have to care about errors from download_file anymore */Or
let res : Path = download_file(url)?; /* res is guaranteed to be a valid Path, you don't have to care about errors from download_file anymore */Which will just forward the error to your caller (but your function has to return
Resultas well), or proceed with the execution if the function succeeded.Finally,
download_file(url).unwrap()is if you can neither ignore, nor handle, nor pass the error to the caller. It will abort if the function fails in any way, and there’s no (practical) way to catch that abort.