Basic Error Handling
Error handling in Rust can be clumsy if you can’t use the question-mark operator.
To achieve happiness, we need to return a Result
which can accept any error.
All errors implement the trait std::error::Error
, and
so any error can convert into a Box<Error>
.
Say we needed to handle both i/o errors and errors from converting
strings into numbers:
# #![allow(unused_variables)] # #fn main() { // box-error.rs use std::fs::File; use std::io::prelude::*; use std::error::Error; fn run(file: &str) -> Result<i32,Box<Error>> { let mut file = File::open(file)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents.trim().parse()?) } #}
So that’s two question-marks for the i/o errors (can’t open file, or can’t read as string)
and one question-mark for the conversion error. Finally, we wrap the result inOk
.
Rust can work out from the return type thatparse
should convert toi32
.It’s easy to create a shortcut for this
Result
type:# #![allow(unused_variables)] # #fn main() { type BoxResult<T> = Result<T,Box<Error>>; #}
However, our programs will have application-specific error conditions, and so
we need to create our own error type. The basic requirements
are straightforward:
- May implement
Debug
- Must implement
Display
- Must implement
Error
Otherwise, your error can do pretty much what it likes.
# #![allow(unused_variables)] # #fn main() { // error1.rs use std::error::Error; use std::fmt; #[derive(Debug)] struct MyError { details: String } impl MyError { fn new(msg: &str) -> MyError { MyError{details: msg.to_string()} } } impl fmt::Display for MyError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f,"{}",self.details) } } impl Error for MyError { fn description(&self) -> &str { &self.details } } // a test function that returns our error result fn raises_my_error(yes: bool) -> Result<(),MyError> { if yes { Err(MyError::new("borked")) } else { Ok(()) } } #}
Typing
Result<T,MyError>
gets tedious and many Rust modules define their own
Result
— e.g.io::Result<T>
is short forResult<T,io::Error>
.In this next example we need to handle the specific error when a string can’t be parsed
as a floating-point number.Now the way that
?
works
is to look for a conversion from the error of the expression to the error that must
be returned. And this conversion is expressed by theFrom
trait.Box<Error>
works as it does because it implementsFrom
for all types implementingError
.At this point you can continue to use the convenient alias
BoxResult
and catch everything
as before; there will be a conversion from our error intoBox<Error>
.
This is a good option for smaller applications. But I want to show other errors can
be explicitly made to cooperate with our error type.
ParseFloatError
implementsError
sodescription()
is defined.# #![allow(unused_variables)] # #fn main() { use std::num::ParseFloatError; impl From<ParseFloatError> for MyError { fn from(err: ParseFloatError) -> Self { MyError::new(err.description()) } } // and test! fn parse_f64(s: &str, yes: bool) -> Result<f64,MyError> { raises_my_error(yes)?; let x: f64 = s.parse()?; Ok(x) } #}
The first
?
is fine (a type always converts to itself withFrom
) and the
second?
will convert theParseFloatError
toMyError
.And the results:
fn main() { println!(" {:?}", parse_f64("42",false)); println!(" {:?}", parse_f64("42",true)); println!(" {:?}", parse_f64("?42",false)); } // Ok(42) // Err(MyError { details: "borked" }) // Err(MyError { details: "invalid float literal" })
Not too complicated, although a little long-winded. The tedious bit is having to
writeFrom
conversions for all the other error types that need to play nice
withMyError
— or you simply lean onBox<Error>
. Newcomers get confused
by the multitude of ways to do the same thing in Rust; there is always another
way to peel the avocado (or skin the cat, if you’re feeling bloodthirsty). The price
of flexibility is having many options. Error-handling for a 200 line program can afford
to be simpler than for a large application. And if you ever want to package your precious
droppings as a Cargo crate, then error handling becomes crucial.Currently, the question-mark operator only works for
Result
, notOption
, and this is
a feature, not a limitation.Option
has aok_or_else
which converts itself into aResult
.
For example, say we had aHashMap
and must fail if a key isn’t defined:# #![allow(unused_variables)] # #fn main() { let val = map.get("my_key").ok_or_else(|| MyError::new("my_key not defined"))?; #}
Now here the error returned is completely clear! (This form uses a closure, so the error value
is only created if the lookup fails.)simple-error for Simple Errors
The simple-error crate provides you with
a basic error type based on a string, as we have defined it here, and a few convenient macros.
Like any error, it works fine withBox<Error>
:#[macro_use] extern crate simple_error; use std::error::Error; type BoxResult<T> = Result<T,Box<Error>>; fn run(s: &str) -> BoxResult<i32> { if s.len() == 0 { bail!("empty string"); } Ok(s.trim().parse()?) } fn main() { println!("{:?}", run("23")); println!("{:?}", run("2x")); println!("{:?}", run("")); } // Ok(23) // Err(ParseIntError { kind: InvalidDigit }) // Err(StringError("empty string"))
bail!(s)
expands toreturn SimpleError::new(s).into();
— return early with a conversion into
the receiving type.You need to use
BoxResult
for mixing theSimpleError
type with other errors, since
we can’t implementFrom
for it, since both the trait and the type come from other crates.error-chain for Serious Errors
For non-trivial applications have a look
at the error_chain crate.
A little macro magic can go a long way in Rust…Create a binary crate with
cargo new --bin test-error-chain
and
change to this directory. EditCargo.toml
and adderror-chain="0.8.1"
to the end.What error-chain does for you is create all the definitions we needed for manually implementing
an error type; creating a struct, and implementing the necessary traits:Display
,Debug
andError
.
It also by default implementsFrom
so strings can be converted into errors.Our first
src/main.rs
file looks like this. All the main program does is callrun
, print out any
errors, and end the program with a non-zero exit code. The macroerror_chain
generates all the
definitions needed, within anerror
module — in a larger program you would put this in its own file.
We need to bring everything inerror
back into global scope because our code will need to see
the generated traits. By default, there will be anError
struct and aResult
defined with that
error.Here we also ask for
From
to be implemented so thatstd::io::Error
will convert into
our error type usingforeign_links
:#[macro_use] extern crate error_chain; mod errors { error_chain!{ foreign_links { Io(::std::io::Error); } } } use errors::*; fn run() -> Result<()> { use std::fs::File; File::open("file")?; Ok(()) } fn main() { if let Err(e) = run() { println!("error: {}", e); std::process::exit(1); } } // error: No such file or directory (os error 2)
The ‘foreign_links’ has made our life easier, since the question mark operator now knows how to
convertstd::io::Error
into ourerror::Error
. (Under the hood, the macro is creating a
From<std::io::Error>
conversion, exactly as spelt out earlier.)All the action happens in
run
; let’s make it print out the first 10 lines of a file given as the
first program argument. There may or may not be such an argument, which isn’t necessarily an
error. Here we want to convert anOption<String>
into aResult<String>
. There are twoOption
methods for doing this conversion, and I’ve picked the simplest one. OurError
type implements
From
for&str
, so it’s straightforward to make an error with a simple text message.# #![allow(unused_variables)] # #fn main() { fn run() -> Result<()> { use std::env::args; use std::fs::File; use std::io::BufReader; use std::io::prelude::*; let file = args().skip(1).next() .ok_or(Error::from("provide a file"))?; let f = File::open(&file)?; let mut l = 0; for line in BufReader::new(f).lines() { let line = line?; println!("{}", line); l += 1; if l == 10 { break; } } Ok(()) } #}
There is (again) a useful little macro
bail!
for ‘throwing’ errors.
An alternative to theok_or
method here could be:# #![allow(unused_variables)] # #fn main() { let file = match args().skip(1).next() { Some(s) => s, None => bail!("provide a file") }; #}
Like
?
it does an early return.The returned error contains an enum
ErrorKind
, which allows us to distinguish between various
kinds of errors. There’s always a variantMsg
(when you sayError::from(str)
) and theforeign_links
has declaredIo
which wraps I/O errors:fn main() { if let Err(e) = run() { match e.kind() { &ErrorKind::Msg(ref s) => println!("msg {}",s), &ErrorKind::Io(ref s) => println!("io {}",s), } std::process::exit(1); } } // $ cargo run // msg provide a file // $ cargo run foo // io No such file or directory (os error 2)
It’s straightforward to add new kinds of errors. Add an
errors
section to theerror_chain!
macro:# #![allow(unused_variables)] # #fn main() { error_chain!{ foreign_links { Io(::std::io::Error); } errors { NoArgument(t: String) { display("no argument provided: '{}'", t) } } } #}
This defines how
Display
works for this new kind of error. And now we can handle
‘no argument’ errors more specifically, feedingErrorKind::NoArgument
aString
value:# #![allow(unused_variables)] # #fn main() { let file = args().skip(1).next() .ok_or(ErrorKind::NoArgument("filename needed".to_string()))?; #}
There’s now an extra
ErrorKind
variant that you must match:fn main() { if let Err(e) = run() { println!("error {}",e); match e.kind() { &ErrorKind::Msg(ref s) => println!("msg {}", s), &ErrorKind::Io(ref s) => println!("io {}", s), &ErrorKind::NoArgument(ref s) => println!("no argument {:?}", s), } std::process::exit(1); } } // cargo run // error no argument provided: 'filename needed' // no argument "filename needed"
Generally, it’s useful to make errors as specific as possible, particularly if this is a library
function! This match-on-kind technique is pretty much the equivalent of traditional exception handling,
where you match on exception types in acatch
orexcept
block.In summary, error-chain creates a type
Error
for you, and definesResult<T>
to bestd::result::Result<T,Error>
.
Error
contains an enumErrorKind
and by default there is one variantMsg
for errors created from
strings. You define external errors withforeign_links
which does two things. First, it creates a new
ErrorKind
variant. Second, it definesFrom
on these external errors so they can be converted to our
error. New error variants can be easily added. A lot of irritating boilerplate code is eliminated.Chaining Errors
But the really cool thing that this crate provides is error chaining.
As a library user, it’s irritating when a method simply just ‘throws’ a generic I/O error. OK, it
could not open a file, fine, but what file? Basically, what use is this information to me?
error_chain
does error chaining which helps solve this problem of over-generic errors. When we
try to open the file, we can lazily lean on the conversion toio::Error
using?
, or chain the error.# #![allow(unused_variables)] # #fn main() { // non-specific error let f = File::open(&file)?; // a specific chained error let f = File::open(&file).chain_err(|| "unable to read the damn file")?; #}
Here’s a new version of the program, with no imported ‘foreign’ errors, just the defaults:
#[macro_use] extern crate error_chain; mod errors { error_chain!{ } } use errors::*; fn run() -> Result<()> { use std::env::args; use std::fs::File; use std::io::BufReader; use std::io::prelude::*; let file = args().skip(1).next() .ok_or(Error::from("filename needed"))?; ///////// chain explicitly! /////////// let f = File::open(&file).chain_err(|| "unable to read the damn file")?; let mut l = 0; for line in BufReader::new(f).lines() { let line = line.chain_err(|| "cannot read a line")?; println!("{}", line); l += 1; if l == 10 { break; } } Ok(()) } fn main() { if let Err(e) = run() { println!("error {}", e); /////// look at the chain of errors... /////// for e in e.iter().skip(1) { println!("caused by: {}", e); } std::process::exit(1); } } // $ cargo run foo // error unable to read the damn file // caused by: No such file or directory (os error 2)
So the
chain_err
method takes the original error, and creates a new error which contains the
original error — this can be continued indefinitely. The closure is expected to return any
value which can be converted into an error.Rust macros can clearly save you a lot of typing.
error-chain
even provides a shortcut that
replaces the whole main program:# #![allow(unused_variables)] # #fn main() { quick_main!(run); #}
(
run
is where all the action takes place, anyway.)
I started doing university lectures on Rust, as well as holding workshops and trainings. One of the parts that evolved from a couple of slides into a full-blown session was everything around error handling in Rust, since it’s so incredibly good!
Not only does it help making impossible states impossible, but there’s also so much detail to it that handling errors – much like everything in Rust – becomes very ergonomic and easy to read and use.
Making impossible states impossible #
In Rust, there are no things like undefined
or null
, nor do you have exceptions like you know it from programming languages like Java or C#. Instead, you use built-in enums to model state:
Option<T>
for bindings that might possibly have no value (e.g.Some(x)
orNone
)Result<T, E>
for results from operations that might error (e.g.Ok(val)
vsErr(error)
)
The difference between the two is very nuanced and depends a lot on the semantics of your code. The way both enums work is very similar though. The most important thing, in my opinion, is that both types request from you to deal with them. Either by explicitly handling all states, or by explicitly ignoring them.
In this article, I want to focus on Result<T, E>
as this one actually contains errors.
Result<T, E>
is an enum with two variants:
enum Result<T, E> {
Ok(T),
Err(E),
}
T
, E
are generics. T
can be any value, E
can be any error. The two variants Ok
and Err
are globally available.
Use Result<T, E>
when you have things that might go wrong. An operation that is expected to succeed, but there might be cases where it doesn’t. Once you have a Result
value, you can do the following:
- Deal with the states!
- Ignore it
- Panic!
- Use fallbacks
- Propagate errors
Let’s see what I mean in detail.
Deal with the error state #
Let’s write a little piece where we want to read a string from a file. It requires us to
- Read a file
- Read a string from this file
Both operations might cause a std::io::Error
because something unforeseen can happen (the file doesn’t exist, or it can’t be read from, etc.). So the function we’re writing can return either a String
or an io::Error
.
use std::io;
use std::fs::File;fn read_username_from_file(path: &str) -> Result<String, io::Error> {
let f = File::open(path);
/* 1 */
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
/* 2 */
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(err) => Err(err),
}
}
This is what happens:
- When we open a file from
path
, it either can return a filehandle to work withOk(file)
, or it causes an errorErr(e)
. Withmatch f
we’re forced to deal with the two possible states. Either we assign the filehandle tof
(notice the shadowing off
), or we return from the function by returning the error. Thereturn
statement here is important as we want to exit the function. - We then want to read the contents into
s
, the string we just created. It again can either succeed or throw an error. The functionf.read_to_string
returns the length of bytes read, so we can safely ignore the value and return anOk(s)
with the string read. In the other case, we just return the same error. Note that I didn’t write a semi-colon at the end of thematch
expression. Since it’s an expression, this is what we return from the function at this point.
This might look very verbose (it is…), but you see two very important aspects of error handling:
- In both cases you’re expected to deal with the two possible states. You can’t continue if don’t do something
- Features like shadowing (binding a value to an existing name) and expressions make even verbose code easy to read and use
The operation we just did is often called unwrapping. Because you unwrap the value that is wrapped inside the enum.
Speaking of unwrapping…
Ignore the errors #
If you’re very confident that your program won’t fail, you can simply .unwrap()
your values using the built-in functions:
fn read_username_from_file(path: &str) -> Result<String, io::Error> {
let mut f = File::open(path).unwrap(); /* 1 */
let mut s = String::new();
f.read_to_string(&mut s).unwrap(); /* 1 */
Ok(s) /* 2 */
}
Here’s what happens:
- In all cases that might cause an error, we’re calling
unwrap()
to get to the value - We wrap the result in an
Ok
variant which we return. We could just returns
and drop theResult<T, E>
in our function signature. We keep it because we use it in the other examples again.
The unwrap()
function itself is very much like what we did in the first step where we dealt with all states:
// result.rsimpl<T, E: fmt::Debug> Result<T, E> {
// ...
pub fn unwrap(&self) -> T {
match self {
Ok(t) => t,
Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", &e),
}
}
// ...
}
unwrap_failed
is a shortcut to the panic!
macro. This means if you use .unwrap()
and you don’t have a successful result, your software crashes. 😱
You might ask yourself: How is this different from errors that just crash the software in other programming languages? The answer is easy: You have to be explicit about it. Rust requires you to do something, even if it’s explicitly allowing to panic.
There are lots of different .unwrap_
functions you can use for various situations. We look at one or two of them further on.
Panic! #
Speaking of panics, you can also panic with your own panic message:
fn read_username_from_file(path: &str) -> Result<String, io::Error> {
let mut f = File::open(path).expect("Error opening file");
let mut s = String::new();
f.read_to_string(&mut s).unwrap("Error reading file to string");
Ok(s)
}
What .expect(...)
does is very similar to unwrap()
impl<T, E: fmt::Debug> Result<T, E> {
// ...
pub fn expect(self, msg: &str) -> T {
match self {
Ok(t) => t,
Err(e) => unwrap_failed(msg, &e),
}
}
}
But, you have your panic messages in your hand, which you might like!
But even if we are explicit at all times, we may want our software not to panic and crash whenever we encounter an error state. We might want to do something useful, like providing fallbacks or … well … actually handling errors.
Fallback values #
Rust has the possibility to use default values on their Result
(and Option
) enums.
fn read_username_from_file(path: &str) -> Result<String, io::Error> {
let mut f = File::open(path).expect("Error opening file");
let mut s = String::new();
f.read_to_string(&mut s).unwrap_or("admin"); /* 1 */
Ok(s)
}
"admin"
might not be the best fallback for a username, but you get the idea. Instead of crashing, we return a default value in the case of an error result. The method.unwrap_or_else
takes a closure for more complex default values.
That’s better! Still, what we’ve learned so far is a trade-off between being very verbose, or allowing for explicit crashes, or maybe having fallback values. But can we have both? Concise code and error safety? We can!
Propagate the error #
One of the features I love most with Rust’s Result
types is the possibility to propagate an error. Both functions that might cause an error have the same error type: io::Error
. We can use the question mark operator after each operation to write code for the happy path (only success results), and return error results if something goes wrong:
fn read_username_from_file(path: &str) -> Result<String, io::Error> {
let mut f = File::open(path)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
In this piece, f
is a file handler, f.read_to_string
saves to s
. If anything goes wrong, we return from the function with Err(io::Error)
. Concise code, but we deal with the error one level above:
fn main() {
match read_username_from_file("user.txt") {
Ok(username) => println!("Welcome {}", username),
Err(err) => eprintln!("Whoopsie! {}", err)
};
}
The great thing about it?
- We are still explicit, we have to do something! You can still find all the spots where errors can happen!
- We can write concise code as if errors wouldn’t exist. Errors still have to be dealt with! Either from us or from the users of our function.
The question mark operator also works on Option<T>
, this also allows for some really nice and elegant code!
Propagating different errors #
The problem is though, that methods like this only work when the error types are the same. If we have two different types of errors, we have to get creative. Look at this slightly modified function, where we open and read files, but then parse the read content into a u64
fn read_number_from_file(filename: &str) -> Result<u64, ???> {
let mut file = File::open(filename)?; /* 1 */ let mut buffer = String::new();
file.read_to_string(&mut buffer)?; /* 1 */
let parsed: u64 = buffer.trim().parse()?; /* 2 */
Ok(parsed)
}
- These two spots can cause
io::Error
, as we know from the previous examples - This operation however can cause a
ParseIntError
The problem is, we don’t know which error we get at compile time. This is entirely up to our code running. We could handle each error through match
expressions and return our own error type. Which is valid, but makes our code verbose again. Or we prepare for “things that happen at runtime”!
Check out our slightly changed function
use std::error;
fn read_number_from_file(filename: &str) -> Result<u64, Box<dyn error::Error>> {
let mut file = File::open(filename)?; /* 1 */
let mut buffer = String::new();
file.read_to_string(&mut buffer)?; /* 1 */
let parsed: u64 = buffer.trim().parse()?; /* 2 */
Ok(parsed)
}
This is what happens:
- Instead of returning an error implementation, we tell Rust that something that implements the
Error
error trait is coming along. - Since we don’t know what this can be at compile-time, we have to make it a trait object:
dyn std::error::Error
. - And since we don’t know how big this will be, we wrap it in a
Box
. A smart pointer that points to data that will be eventually on the heap
A Box<dyn Trait>
enables dynamic dispatch in Rust: The possibility to dynamically call a function that is not known at compile time. For that, Rust introduces a vtable that keeps pointers to the actual implementations. At runtime, we use these pointers to invoke the appropriate function implementations.
And now, our code is concise again, and our users have to deal with the eventual error.
The first question I get when I show this to folks in my courses is: But can we eventually check which type of error has happened? We can! The downcast_ref()
method allows us to get back to the original type.
fn main() {
match read_number_from_file("number.txt") {
Ok(v) => println!("Your number is {}", v),
Err(err) => {
if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
eprintln!("Error during IO! {}", io_err)
} else if let Some(pars_err) = err.downcast_ref::<ParseIntError>() {
eprintln!("Error during parsing {}", pars_err)
}
}
};
}
Groovy!
Custom errors #
It’s getting even better and more flexible if you want to create custom errors for your operations. To use custom errors, your error structs have to implement the std::error::Error
trait. This can be a classic struct, a tuple struct or even a unit struct.
You don’t have to implement any functions of std::error::Error
, but you need to implement both the Debug
and the Display
trait. The reasoning is that errors want to be printed somewhere. Here’s how an example looks like:
#[derive(Debug)] /* 1 */
pub struct ParseArgumentsError(String); /* 2 */impl std::error::Error for ParseArgumentsError {} /* 3 */
/* 4 */
impl Display for ParseArgumentsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
- We derive the
Debug
trait. - Our
ParseArgumentsError
is a tuple struct with one element: A custom message - We implement
std::error::Error
forParseArgumentsError
. No need to implement anything else - We implement
Display
, where we print out the single element of our tuple.
And that’s it!
Anyhow… #
Since a lot of the things you just learned a very common, there are of course crates available that abstract most of it. The fantastic anyhow crate is one of them and gives you trait object-based error handling with convenience macros and types.
Bottom line #
This is a very quick primer on error handling in Rust. There is of course more to it, but it should get you started! This is also my first technical Rust article, and I hope many more are coming. Let me know if you liked it and if you find any … haha … errors (ba-dum-ts 🥁), I’m just a tweet away.
Struct std::io::Error
pub struct Error { /* fields omitted */ }
The error type for I/O operations of the Read
, Write
, Seek
, and associated traits.
Errors mostly originate from the underlying OS, but custom instances of Error
can be created with crafted error messages and a particular value of ErrorKind
.
impl Error
[src]
pub fn new<E>(kind: ErrorKind, error: E) -> Error where
E: Into<Box<dyn Error + Send + Sync>>,
[src]
E: Into<Box<dyn Error + Send + Sync>>,
Creates a new I/O error from a known kind of error as well as an arbitrary error payload.
This function is used to generically create I/O errors which do not originate from the OS itself. The error
argument is an arbitrary payload which will be contained in this Error
.
use std::io::{Error, ErrorKind}; // errors can be created from strings let custom_error = Error::new(ErrorKind::Other, "oh no!"); // errors can also be created from other errors let custom_error2 = Error::new(ErrorKind::Interrupted, custom_error);
pub fn last_os_error() -> Error
[src]
Returns an error representing the last OS error which occurred.
This function reads the value of errno
for the target platform (e.g. GetLastError
on Windows) and will return a corresponding instance of Error
for the error code.
use std::io::Error; println!("last OS error: {:?}", Error::last_os_error());
pub fn from_raw_os_error(code: i32) -> Error
[src]
Creates a new instance of an Error
from a particular OS error code.
On Linux:
use std::io; let error = io::Error::from_raw_os_error(22); assert_eq!(error.kind(), io::ErrorKind::InvalidInput);
On Windows:
use std::io; let error = io::Error::from_raw_os_error(10022); assert_eq!(error.kind(), io::ErrorKind::InvalidInput);
pub fn raw_os_error(&self) -> Option<i32>
[src]
Returns the OS error that this error represents (if any).
If this Error
was constructed via last_os_error
or from_raw_os_error
, then this function will return Some
, otherwise it will return None
.
use std::io::{Error, ErrorKind}; fn print_os_error(err: &Error) { if let Some(raw_os_err) = err.raw_os_error() { println!("raw OS error: {:?}", raw_os_err); } else { println!("Not an OS error"); } } fn main() { // Will print "raw OS error: ...". print_os_error(&Error::last_os_error()); // Will print "Not an OS error". print_os_error(&Error::new(ErrorKind::Other, "oh no!")); }
pub fn get_ref(&self) -> Option<&(dyn Error + Send + Sync + 'static)>
[src]1.3.0
Returns a reference to the inner error wrapped by this error (if any).
If this Error
was constructed via new
then this function will return Some
, otherwise it will return None
.
use std::io::{Error, ErrorKind}; fn print_error(err: &Error) { if let Some(inner_err) = err.get_ref() { println!("Inner error: {:?}", inner_err); } else { println!("No inner error"); } } fn main() { // Will print "No inner error". print_error(&Error::last_os_error()); // Will print "Inner error: ...". print_error(&Error::new(ErrorKind::Other, "oh no!")); }
pub fn get_mut(&mut self) -> Option<&mut (dyn Error + Send + Sync + 'static)>
[src]1.3.0
Returns a mutable reference to the inner error wrapped by this error (if any).
If this Error
was constructed via new
then this function will return Some
, otherwise it will return None
.
use std::io::{Error, ErrorKind}; use std::{error, fmt}; use std::fmt::Display; #[derive(Debug)] struct MyError { v: String, } impl MyError { fn new() -> MyError { MyError { v: "oh no!".to_string() } } fn change_message(&mut self, new_message: &str) { self.v = new_message.to_string(); } } impl error::Error for MyError {} impl Display for MyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "MyError: {}", &self.v) } } fn change_error(mut err: Error) -> Error { if let Some(inner_err) = err.get_mut() { inner_err.downcast_mut::<MyError>().unwrap().change_message("I've been changed!"); } err } fn print_error(err: &Error) { if let Some(inner_err) = err.get_ref() { println!("Inner error: {}", inner_err); } else { println!("No inner error"); } } fn main() { // Will print "No inner error". print_error(&change_error(Error::last_os_error())); // Will print "Inner error: ...". print_error(&change_error(Error::new(ErrorKind::Other, MyError::new()))); }
pub fn into_inner(self) -> Option<Box<dyn Error + Send + Sync>>
[src]1.3.0
Consumes the Error
, returning its inner error (if any).
If this Error
was constructed via new
then this function will return Some
, otherwise it will return None
.
use std::io::{Error, ErrorKind}; fn print_error(err: Error) { if let Some(inner_err) = err.into_inner() { println!("Inner error: {}", inner_err); } else { println!("No inner error"); } } fn main() { // Will print "No inner error". print_error(Error::last_os_error()); // Will print "Inner error: ...". print_error(Error::new(ErrorKind::Other, "oh no!")); }
pub fn kind(&self) -> ErrorKind
[src]
Returns the corresponding ErrorKind
for this error.
use std::io::{Error, ErrorKind}; fn print_error(err: Error) { println!("{:?}", err.kind()); } fn main() { // Will print "Other". print_error(Error::last_os_error()); // Will print "AddrInUse". print_error(Error::new(ErrorKind::AddrInUse, "oh no!")); }
impl Debug for Error
[src]
impl Display for Error
[src]
impl Error for Error
[src]
fn description(&self) -> &str
[src]
👎 Deprecated since 1.42.0: use the Display impl or to_string()
if let Err(e) = "xc".parse::<u32>() { // Print
e itself, no need for description(). eprintln!("Error: {}", e); }
Read more
fn cause(&self) -> Option<&dyn Error>
[src]
👎 Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
fn source(&self) -> Option<&(dyn Error + 'static)>
[src]
The lower-level source of this error, if any. Read more
fn backtrace(&self) -> Option<&Backtrace>
[src]
🔬 This is a nightly-only experimental API. (backtrace #53487)
Returns a stack backtrace, if available, of where this error occurred. Read more
impl From<ErrorKind> for Error
[src]1.14.0
Intended for use for errors not exposed to the user, where allocating onto the heap (for normal construction via Error::new) is too costly.
fn from(kind: ErrorKind) -> Error
[src]
Converts an ErrorKind
into an Error
.
This conversion allocates a new error with a simple representation of error kind.
use std::io::{Error, ErrorKind}; let not_found = ErrorKind::NotFound; let error = Error::from(not_found); assert_eq!("entity not found", format!("{}", error));
impl<W> From<IntoInnerError<W>> for Error
[src]
impl From<NulError> for Error
[src]
impl !RefUnwindSafe for Error
impl Send for Error
impl Sync for Error
impl Unpin for Error
impl !UnwindSafe for Error
impl<T> Any for T where
T: 'static + ?Sized,
[src]
T: 'static + ?Sized,
impl<T> Borrow<T> for T where
T: ?Sized,
[src]
T: ?Sized,
fn borrow(&self) -> &Tⓘ
Notable traits for &'_ mut F
impl<'_, F> Future for &'_ mut F where
F: Unpin + Future + ?Sized,
type Output = <F as Future>::Output;
impl<'_, I> Iterator for &'_ mut I where
I: Iterator + ?Sized,
type Item = <I as Iterator>::Item;
impl<R: Read + ?Sized, '_> Read for &'_ mut R
impl<W: Write + ?Sized, '_> Write for &'_ mut W
[src]
Notable traits for &'_ mut F
impl<'_, F> Future for &'_ mut F where F: Unpin + Future + ?Sized, type Output = <F as Future>::Output; impl<'_, I> Iterator for &'_ mut I where I: Iterator + ?Sized, type Item = <I as Iterator>::Item; impl<R: Read + ?Sized, '_> Read for &'_ mut R impl<W: Write + ?Sized, '_> Write for &'_ mut W
Immutably borrows from an owned value. Read more
impl<T> BorrowMut<T> for T where
T: ?Sized,
[src]
T: ?Sized,
fn borrow_mut(&mut self) -> &mut Tⓘ
Notable traits for &'_ mut F
impl<'_, F> Future for &'_ mut F where
F: Unpin + Future + ?Sized,
type Output = <F as Future>::Output;
impl<'_, I> Iterator for &'_ mut I where
I: Iterator + ?Sized,
type Item = <I as Iterator>::Item;
impl<R: Read + ?Sized, '_> Read for &'_ mut R
impl<W: Write + ?Sized, '_> Write for &'_ mut W
[src]
Notable traits for &'_ mut F
impl<'_, F> Future for &'_ mut F where F: Unpin + Future + ?Sized, type Output = <F as Future>::Output; impl<'_, I> Iterator for &'_ mut I where I: Iterator + ?Sized, type Item = <I as Iterator>::Item; impl<R: Read + ?Sized, '_> Read for &'_ mut R impl<W: Write + ?Sized, '_> Write for &'_ mut W
Mutably borrows from an owned value. Read more
impl<T> From<T> for T
[src]
fn from(t: T) -> T
[src]
Performs the conversion.
impl<T, U> Into<U> for T where
U: From<T>,
[src]
U: From<T>,
fn into(self) -> U
[src]
Performs the conversion.
impl<T> ToString for T where
T: Display + ?Sized,
[src]
T: Display + ?Sized,
impl<T, U> TryFrom<U> for T where
U: Into<T>,
[src]
U: Into<T>,
impl<T, U> TryInto<U> for T where
U: TryFrom<T>,
[src]
U: TryFrom<T>,
1.0.0[−][src]Struct std::io::Error
The error type for I/O operations of the Read
, Write
, Seek
, and
associated traits.
Errors mostly originate from the underlying OS, but custom instances of
Error
can be created with crafted error messages and a particular value of
ErrorKind
.
impl Error
[src]
pub fn new<E>(kind: ErrorKind, error: E) -> Error where
E: Into<Box<dyn Error + 'static + Sync + Send>>,
[src]
E: Into<Box<dyn Error + 'static + Sync + Send>>,
Creates a new I/O error from a known kind of error as well as an
arbitrary error payload.
This function is used to generically create I/O errors which do not
originate from the OS itself. The error
argument is an arbitrary
payload which will be contained in this Error
.
use std::io::{Error, ErrorKind}; let custom_error = Error::new(ErrorKind::Other, "oh no!"); let custom_error2 = Error::new(ErrorKind::Interrupted, custom_error);
pub fn last_os_error() -> Error
[src]
Returns an error representing the last OS error which occurred.
This function reads the value of errno
for the target platform (e.g.
GetLastError
on Windows) and will return a corresponding instance of
Error
for the error code.
use std::io::Error; println!("last OS error: {:?}", Error::last_os_error());
pub fn from_raw_os_error(code: i32) -> Error
[src]
Creates a new instance of an Error
from a particular OS error code.
On Linux:
use std::io; let error = io::Error::from_raw_os_error(22); assert_eq!(error.kind(), io::ErrorKind::InvalidInput);
On Windows:
use std::io; let error = io::Error::from_raw_os_error(10022); assert_eq!(error.kind(), io::ErrorKind::InvalidInput);
pub fn raw_os_error(&self) -> Option<i32>
[src]
Returns the OS error that this error represents (if any).
If this Error
was constructed via last_os_error
or
from_raw_os_error
, then this function will return Some
, otherwise
it will return None
.
use std::io::{Error, ErrorKind}; fn print_os_error(err: &Error) { if let Some(raw_os_err) = err.raw_os_error() { println!("raw OS error: {:?}", raw_os_err); } else { println!("Not an OS error"); } } fn main() { print_os_error(&Error::last_os_error()); print_os_error(&Error::new(ErrorKind::Other, "oh no!")); }
pub fn get_ref(&self) -> Option<&(dyn Error + 'static + Sync + Send)>
1.3.0[src]
Returns a reference to the inner error wrapped by this error (if any).
If this Error
was constructed via new
then this function will
return Some
, otherwise it will return None
.
use std::io::{Error, ErrorKind}; fn print_error(err: &Error) { if let Some(inner_err) = err.get_ref() { println!("Inner error: {:?}", inner_err); } else { println!("No inner error"); } } fn main() { print_error(&Error::last_os_error()); print_error(&Error::new(ErrorKind::Other, "oh no!")); }
pub fn get_mut(&mut self) -> Option<&mut (dyn Error + 'static + Sync + Send)>
1.3.0[src]
Returns a mutable reference to the inner error wrapped by this error
(if any).
If this Error
was constructed via new
then this function will
return Some
, otherwise it will return None
.
use std::io::{Error, ErrorKind}; use std::{error, fmt}; use std::fmt::Display; #[derive(Debug)] struct MyError { v: String, } impl MyError { fn new() -> MyError { MyError { v: "oh no!".to_string() } } fn change_message(&mut self, new_message: &str) { self.v = new_message.to_string(); } } impl error::Error for MyError { fn description(&self) -> &str { &self.v } } impl Display for MyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "MyError: {}", &self.v) } } fn change_error(mut err: Error) -> Error { if let Some(inner_err) = err.get_mut() { inner_err.downcast_mut::<MyError>().unwrap().change_message("I've been changed!"); } err } fn print_error(err: &Error) { if let Some(inner_err) = err.get_ref() { println!("Inner error: {}", inner_err); } else { println!("No inner error"); } } fn main() { print_error(&change_error(Error::last_os_error())); print_error(&change_error(Error::new(ErrorKind::Other, MyError::new()))); }
pub fn into_inner(self) -> Option<Box<dyn Error + 'static + Sync + Send>>
1.3.0[src]
Consumes the Error
, returning its inner error (if any).
If this Error
was constructed via new
then this function will
return Some
, otherwise it will return None
.
use std::io::{Error, ErrorKind}; fn print_error(err: Error) { if let Some(inner_err) = err.into_inner() { println!("Inner error: {}", inner_err); } else { println!("No inner error"); } } fn main() { print_error(Error::last_os_error()); print_error(Error::new(ErrorKind::Other, "oh no!")); }
pub fn kind(&self) -> ErrorKind
[src]
Returns the corresponding ErrorKind
for this error.
use std::io::{Error, ErrorKind}; fn print_error(err: Error) { println!("{:?}", err.kind()); } fn main() { print_error(Error::last_os_error()); print_error(Error::new(ErrorKind::AddrInUse, "oh no!")); }
impl<T> ToString for T where
T: Display + ?Sized,
[src]
T: Display + ?Sized,
impl<T, U> Into<U> for T where
U: From<T>,
[src]
U: From<T>,
fn into(self) -> U
[src]
Performs the conversion.
impl<T> From<T> for T
[src]
fn from(t: T) -> T
[src]
Performs the conversion.
impl<T, U> TryFrom<U> for T where
U: Into<T>,
[src]
U: Into<T>,
impl<T, U> TryInto<U> for T where
U: TryFrom<T>,
[src]
U: TryFrom<T>,
impl<T> BorrowMut<T> for T where
T: ?Sized,
[src]
T: ?Sized,
impl<T> Borrow<T> for T where
T: ?Sized,
[src]
T: ?Sized,
impl<T> Any for T where
T: 'static + ?Sized,
[src]
T: 'static + ?Sized,