Is my understanding of the following Rust "reqwest" code correct? - error-handling

I've been toying around with Rust and have come across the following code:
fn request(&url) -> Result<(), Box<dyn std::error::Error>> {
let mut res = reqwest::get(&url)?;
let mut body = String::new();
res.read_to_string(&mut body)?;
println!("Status: {}", res.status());
println!("Headers:\n{:#?}", res.headers());
println!("Body:\n{}", body);
Ok(())
}
It is my understanding that:
fn request(&url) -> Result<(), Box<dyn std::error::Error>> {
Defines a function that has a single (borrowed) parameter and uses Result to handle errors.
let mut res = reqwest::get(&url)?;
Defines a mutable variable to store the response object from the reqwest crate's get method.
let mut body = String::new();
Defines a mutable variable to store the responseText string.
res.read_to_string(&mut body)?;
This method stores the responseText in the body variable.
println!("Status: {}", res.status());
println!("Headers:\n{:#?}", res.headers());
println!("Body:\n{}", body);
Prints three formatted strings (with trailing new lines) containing the response status, headers and body.
Ok(())
Handles errors via Result..?
Questions:
What do the empty parenthesis in Result<() and OK(()) mean/do?
What is Box<dyn std::error::Error>?

You're absolutely correct in your understanding.
A Result is an Enum which can either be "Ok" or "Err" - if Ok, then there can be some value of okayness (a result, response, data, output, whatever); similarly, if Err, then there's some concrete error you may want to communicate. With that let's break down the result.
The should be read like this: Result<TypeOfValueIfOkay, TypeOfErrorWhenNotOkay>. These two sub-types can be anything, but they have to be something - can't just ignore it.
So if TypeOfValueIfOkay has to be something, but if you don't want to return something, you can return an empty Tuple. That's the () in Result. It's just efficiently saying "I return nothing at all when everything goes well".
So then the second part TypeOfErrorWhenNotOkay can also just be any type - a string, an int, whatever. It helps for the type to implement the std::error::Error trait helping callers standardize a bit.
Returning "some dynamic object but that implements trait std::error::Error" requires Rust to know the exact size of this value if it is to return it on the caller's stack (the caller's stack needs to be sized to accept it.)
This is where the Box type comes in - it pushes the actual value onto the heap and holds a pointer to it (which can be of predictable fixed size no matter the actual value on the heap.) The <dyn std::error::Error> is an assurance that whatever the boxed value is, it implements the Error trait.
So now the final Ok(()) makes sense. If you read Ok(value): it says the Result enum is variant Ok with the value of "empty tuple" (), i.e. nothing.

Related

Rust: Read and map lines from stdin and handling different error types

I'm learning Rust and trying to solve some basic algorithm problems with it. In many cases, I want to read lines from stdin, perform some transformation on each line and return a vector of resulting items. One way I did this was like this:
// Fully working Rust code
let my_values: Vec<u32> = stdin
.lock()
.lines()
.filter_map(Result::ok)
.map(|line| line.parse::<u32>())
.filter_map(Result::ok)
.map(|x|x*2) // For example
.collect();
This works but of course silently ignores any errors that may occur. Now what I woud like to do is something along the lines of:
// Pseudo-ish code
let my_values: Result<Vec<u32>, X> = stdin
.lock()
.lines() // Can cause std::io::Error
.map(|line| line.parse::<u32>()) // Can cause std::num::ParseIntError
.map(|x| x*2)
.collect();
Where X is some kind of error type that I can match on afterwards. Preferably I want to perform the whole operation on one line at a time and immediately discard the string data after it has been parsed to an int.
I think I need to create some kind of Enum type to hold the various possible errors, possibly like this:
#[derive(Debug)]
enum InputError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
}
However, I don't quite understand how to put everything together to make it clean and avoid having to explicitly match and cast everywhere. Also, is there some way to automatically create these enum error types or do I have to explicilty enumerate them every time I do this?
You're on the right track.
The way I'd approach this is by using the enum you've defined,
then add implementations of From for the error types you're interested in.
That will allow you to use the ? operator on your maps to get the kind of behaviour you want.
#[derive(Debug)]
enum MyError {
IOError(std::io::Error),
ParseIntError(std::num::ParseIntError),
}
impl From<std::io::Error> for MyError {
fn from(e:std::io::Error) -> MyError {
return MyError::IOError(e)
}
}
impl From<std::num::ParseIntError> for MyError {
fn from(e:std::num::ParseIntError) -> MyError {
return MyError::ParseIntError(e)
}
}
Then you can implement the actual transform as either
let my_values: Vec<_> = stdin
.lock()
.lines()
.map(|line| -> Result<u32,MyError> { Ok(line?.parse::<u32>()?*2) } )
.collect();
which will give you one entry for each input, like: {Ok(x), Err(MyError(x)), Ok(x)}.
or you can do:
let my_values: Result<Vec<_>,MyError> = stdin
.lock()
.lines()
.map(|line| -> Result<u32,MyError> { Ok(line?.parse::<u32>()?*2) } )
.collect();
Which will give you either Err(MyError(...)) or Ok([1,2,3])
Note that you can further reduce some of the error boilerplate by using an error handling crate like snafu, but in this case it's not too much.

Why can't the Option.expect() message be downcast as a &'static str when a panic is handled with catch_unwind?

I have the following code:
use std::thread;
use std::panic;
pub fn main(){
thread::spawn(move || {
panic::catch_unwind(|| {
// panic!("Oh no! A horrible error.");
let s: Option<u32> = None;
s.expect("Nothing was there!");
})
})
.join()
.and_then(|result| {
match result {
Ok(ref val) => {
println!("No problems. Result was: {:?}", val);
}
Err(ref err) => {
if let Some(err) = err.downcast_ref::<&'static str>() {
println!("Error: {}", err);
} else {
println!("Unknown error type: {:?}", err);
}
}
}
result
});
}
When I trigger a panic! directly (by uncommenting the line in the code above), then I get an output which includes my error message:
Error: Oh no! A horrible error.
But, if I use Option::expect(&str), as above, then the message cannot be downcast to &'static str, so I can't get the error message out:
Unknown error type: Any
How can I get the error message, and how would I find the correct type to downcast to in the general case?
Option::expect expects a message as a &str, i.e. a string slice with any lifetime. You can't coerce a &str to a &'static str, as the string slice may refer to the interior of a String or Box<str> that could be freed at any time. If you were to keep a copy of the &'static str around, you would be able to use it after the String or Box<str> has been dropped, and that would be undefined behavior.
An importail detail is that the Any trait cannot hold any lifetime information (hence the 'static bound), as lifetimes in Rust are erased at compile time. Lifetimes are used by the compiler to validate your program, but a program cannot distinguish a &'a str from a &'b str from a &'static str at runtime.
[...] how would I find the correct type to downcast to in the general case?
Unfortunately, it's not easy. Any has a method (unstable as of Rust 1.15.1) named get_type_id that lets you obtain the TypeId of the concrete object referred to by the Any. That still doesn't tell you explicitly what type that is, as you still have to figure out which type this TypeId belongs to. You would have to get the TypeId of many types (using TypeId::of) and see if it matches the one you got from the Any, but you could do the same with downcast_ref.
In this instance, it turns out that the Any is a String. Perhaps Option::expect could eventually be specialized such that it panics with the string slice if its lifetime is 'static and only allocates a String if it's not 'static.
Like Francis said, you can't in general discover and cast to the type of a panic. However, that being said, panics have the following rules:
If you panic! with a single argument, the panic will have that type. Typically this is &'static str.
If you panic! with more than one argument, the arguments will be treated as format! parameters and used to create a String argument.
These rules are documented in the panic documentation: https://doc.rust-lang.org/std/panic/fn.catch_unwind.html.
With these rules in mind, we can write a function to extract the message from a panic in any case where there is a message available to be extracted, which in practice works most of the time, because most of the time the message is either &'static str or String:
pub fn get_panic_message(panic: &Box<dyn Any + Send>) -> Option<&str> {
panic
// Try to convert it to a String, then turn that into a str
.downcast_ref::<String>()
.map(String::as_str)
// If that fails, try to turn it into a &'static str
.or_else(|| panic.downcast_ref::<&'static str>().map(Deref::deref))
}
I use this exact function in an assertions library I wrote a while ago; you can see some examples of its use in the relevant test suite.

Object oriented design patterns for error checking

I have written the following function that reads the contents of a text file and panic!s if an error is encountered.
fn get_file_contents(name: String) -> Result<String, io::Error> {
let mut f = try!(File::open(name));
let mut contents = String::new();
try!(f.read_to_string(&mut contents));
Ok(contents)
}
And the contents are extracted from the Result using:
let file_contents = match get_file_contents(file_name) {
Ok(contents) => contents,
Err(err) => panic!("{}", err)
};
I am now trying to reimplement this in an object oriented manner using structures and implementations. I created the following structure:
struct FileReader {
file_name: String,
file_contents: String,
}
and implemented the following methods:
impl FileReader {
fn new(fname: &str) -> FileReader {
FileReader {
file_name: fname.to_string(),
file_contents: String::new(),
}
}
fn get_file_contents(&mut self) {
let mut f = match File::open(&self.file_name) {
Ok(file) => file,
Err(err) => panic!("{}", err)
};
match f.read_to_string(&mut self.file_contents) {
Ok(size) => size,
Err(err) => panic!("{}", err)
};
}
}
In the OO approach, I haven't used the try! macro as I don't want the method to return any value. Is my OO implementation of get_file_contents a typical way of achieving this functionality? If not, can you please suggest an alternative way?
In the OO approach, I haven't used the try! macro as I don't want the method to return any value.
It's unclear why you think that "object oriented" means "doesn't return a value". If an error can occur, the code should indicate that.
Many languages have the equivalent of exceptions — out of band values that are thrown (also known as "returned") from a function or method. Note that this means that these languages allow for two disjoint types to be returned from a given function: the "normal" type and the "exceptional" type. That is a close equivalent for Rust's Result: Result<NormalType, ExceptionalType>.
Exceptional isn't a great term for this, as you should expect that opening a file should fail. There's an infinite number of ways that it could not work, but only a narrow subset of ways that it can succeed.
Panicking is closer to "kill the entire program / thread right now". Unlike C, you are forced to either deal with a problem, pass it back to the caller, or kill the program (panic).
If you would have thrown an exception in a language that supports them, use a Result. If you would have killed the program, or don't want to handle an error, use a panic.
If you want to panic in your particular case, use unwrap, or even better, expect:
fn get_file_contents(&mut self) {
let mut f = File::open(&self.file_name).expect("Couldn't open file");
f.read_to_string(&mut self.file_contents).expect("Couldn't read file");
}
seems kind of clunky to have to deal with the Result for each method.
Which is why the Error Handling section of The Rust Programming Language spends a good amount of time discussing the try! macro:
A cornerstone of error handling in Rust is the try! macro. The try! macro abstracts case analysis like combinators, but unlike combinators, it also abstracts control flow. Namely, it can abstract the early return pattern seen above.
(this makes more sense in context of the page)
I don't want my code to try and recover from the error (most likely caused by the file not being found) - I want it to print a useful error message and then die
Then by all means, panic. There's more succinct AND more detailed ways to do it (as shown above).

Are there any conventions for aggregating multiple errors as the causes of another error?

I'm writing a function that iterates over a vector of Result and returns success if they all were successful, or an error if any failed. Limitations in error::Error are frustrating me and I'm not sure how to work around them. Currently I have something like:
let mut errors = Vec::new();
for result in results {
match result {
Err(err) => errors.push(err),
Ok(success) => { ... }
}
}
if errors.is_empty() {
return Ok(())
else {
return Err(MyErrorType(errors))
}
The problem with my current approach is that I can only set one error to be the cause of MyErrorType, and my error's description needs to be a static String so I can't include the descriptions of each of the triggering failures. All of the failures are potentially relevant to the caller.
There is no convention that I know of, and indeed I have never had the issue of attempting to report multiple errors at once...
... that being said, there are two points that may help you:
There is no limitation that the description be a 'static String, you are likely confusing &'static str and &str. In fn description(&self) -> &str, the lifetime of str is linked to the lifetime of self (lifetime elision) and therefore an embedded String satisfies the constraints
Error is an interface to deal with errors uniformly. In this case, indeed, only a single cause was foreseen, however it does not preclude a more specific type to aggregate multiple causes and since Error allows downcasting (Error::is, Error::downcast, ...) the more specific type can be retrieved by the handler and queried in full
As such, I would suggest that you create a new concrete type solely dedicated to holding multiple errors (in a Vec<Box<Error>>), and implementing the Error interface. It's up to you to decide on the description and cause it will expose.
A single type will let your clients test more easily for downcasting than having an unknown (and potentially growing as time goes) number of potential downcast targets.
expanding a bit on point 1 of Matthieu's good answer.
The point where you're likely running into trouble (I know I did when I tried to implement Error) is that you want to have a dynamic description().
// my own error type
#[derive(Debug)] struct MyError { value: u8 }
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "bummer! Got a {}", self.value)
}
}
// I am now tempted to add the problematic value dynamically
// into the description, but I run into trouble with lifetimes
// this DOES NOT COMPILE because the String I'm building
// goes out of scope and I can't return a reference to it
impl error::Error for MyError {
fn description(&self) -> &str {
&format!("can't put a {} here!", self.value)
}
}
solution 1
Don't dynamically build description(). Just use a static str. This is what most implementations of Error on github seem to do.
If you need to retrieve and display (or log) the value you can always access it from your MyError type. Plus Display (that you must implement for all Error impls) does allow you to create dynamic strings.
I created a contrived example on the playground that shows how to track multiple errors.
solution 2
(what Matthieu is suggesting) you can store the error message in the error itself.
#[derive(Debug)] struct MyError { value: u8, msg: String }
impl MyError {
fn new(value: u8) -> MyError {
MyError { value: value, msg: format!("I don't like value {}", value) }
}
}
// now this works because the returned &str has the same lifetime
// as self
impl error::Error for MyError {
fn description(&self) -> &str {
&self.msg
}
}

How can I get user input without receiving an "Unsed Variable" warning?

I'm taking a look at Rust and decided to build a small program that takes a user's input and prints it, but also want to do some math stuff with it for practice. Currently, this is how I am taking user input:
let mut number = String::new();
let input = io::stdin().read_line(&mut number)
.ok()
.expect("Failed to read line");
println!("You entered {}", number);
However, although I do get the correct input this way, Cargo gives me the following warning:
src/main.rs:10:9: 10:14 warning: unused variable: input, #[warn(unused_variables)] on by default
src/main.rs:10 let input = reader.read_line(&mut number)
If I were to just use the input variable, no matter what number I enter I would get a "2" in return when I print the number.
How can I avoid the warning? Is there another way for me to take input without creating 2 variable bindings?
You can simply not write the value to a variable. As long as the type of the value is not marked must_use, you can ignore the value.
let mut number = String::new();
io::stdin().read_line(&mut number)
.ok()
.expect("Failed to read line");
println!("You entered {}", number);
[commercial]
You can use the text_io crate for super short and readable input like
let i: i32 = read!()
let tup: (i32, String) = read!("{}, {}");
[/commercial]
It creates a warning because you are allocating space for a variable that is never used.
When faced with such warning you can either replace offending variable with _
let _ = io::stdin().read_line(&mut number) ...
or as ker noted just remove the variable altogether
io::stdin().read_line(&mut number)...
The _ will also work in other situation like parameters or in match clauses.
One additional option is to add #[allow(unused_variables)] in the module or crate and disable unused variable warnings. Although, I don't recommend it.