Should I ditch using `and_then` and always use the `?` operator? - error-handling

I want to write something like this, but it won't compile due to a mismatch between types:
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
let val = std::env::args()
.nth(1)
.ok_or("1 arg is expected")
.and_then(std::fs::File::open)
.and_then(serde_yaml::from_reader)?;
}
Because adding a map_err to every closure seems sluggish and 'boiler platy', I replace it with something like:
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
let val = serde_yaml::from_reader(std::fs::File::open(
std::env::args().nth(1).ok_or("1 arg is expected")?,
)?)?;
}
The first one feels more natural and reads like English while the second feels sort of backwards.
Should I ditch the and_then and always use the ? operator?
If not, is there a way to make result combinator as smooth as the ? operator?

Should I ditch the and_then and always use the ? operator ?
That’s a personal judgement and only you can answer it.
Is there a way to make result combinator as smooth as the ? operator ?
Frankly speaking no. ? performs conversions « implicitly » (not really implicitly since it’s very much part of its job, but the conversion doesn’t have to be invoked separately, maybe « tersely »?), and_then does not. That means when using and_then, you have to perform these conversions yourself. That seems logical.
You might be able to build a convenience macro for this tho. Or maybe add an extension method or wrapper type which can perform those conversions under the cover.

The reason why your first expression doesn't work is because the error types are not matching. To be more specific,
let val = std::env::args()
.nth(1)
.ok_or("1 arg is expected")
.and_then(std::fs::File::open)
.and_then(serde_yaml::from_reader)?;
std::env::args().nth(1) returns a Option<T>. If you look at the ok_or signature, which is pub fn ok_or<E>(self, err: E) -> Result<T, E>. This means for your case, ok_or("1 arg is expected") your return type is Result<T, &str>. So your error type here is &str because in ok_or, you pass a string slice as an Error type.
If you take a look at the and_then method, the signature is pub fn and_then<U, F>(self, op: F) -> Result<U, E> where F: FnOnce(T) -> Result<U, E>, basically this means the Function you passed in and_then should have the same error type as the original Result, which is a &str. This is same as the line .and_then(serde_yaml::from_reader), the error types needs to be consistent for all the "chaining" and_then functions.
If you really want, you can do the following way to make your code compile. Or you can create a uniform error so no mismatch error types. Then you can use the ok_or(...).and_then(...).and_then(...) kind of writing. You just need to match the error types.
working example
fn custom_fs_open(path: String) -> Result<String, &'static str>{
// actual logics to open fs
Ok(String::from("abc"))
}
fn custom_serde_yaml(buf: String) -> Result<String, &'static str>{
// actual logics to do serde convertion
Ok(String::from("cde"))
}
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
let val = std::env::args()
.nth(1)
.ok_or("1 arg is expected")
.and_then(custom_fs_open)
.and_then(custom_serde_yaml)?;
Ok(())
}

Related

Why does a standard error need to be boxed in main, but not an io error?

Take the following code snippet:
fn main() -> std::result::Result<(), std::io::Error> {
println!("Bonjour le Monde");
Ok(())
}
This is perfectly fine code, but I wondered what would happen if I changed the type of the error to std::error::Error:
fn main() -> std::result::Result<(), std::error::Error> {
println!("Bonjour le Monde");
Ok(())
}
This is not good anymore:
error[E0277]: the size for values of type (dyn std::error::Error + 'static) cannot be known at compilation time
I fixed it like this:
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
println!("Bonjour le Monde");
Ok(())
}
So why does the main function require Box<> for standard errors, but not for io errors?
std::error::Error is a trait included in the standard library. As Rust points out, it cannot determine how much memory will be need to store an Error at compiled time since a structure of any size could potentially be used. As such, you need to use some form of indirection, such as a reference, a pointer, or, in this case, a Box. See TRPL Ch.17 for more information about using trait objects.
In contrast, std::io::Error is a structure from the std::io module (which also happens to implement std::error::Error). Rust does know large this structure is, so it can create a monomorphization of Result that uses it.

Can you return a Result that works with any possible error type?

I want to use multiple libraries that each have their own error types. I don't really care about each specific crate's error type and I want to use the ? idiom to use the methods of those crates that return a Result type.
I don't want to unwrap the values either, that would cause a panic if it hits an error. I might just want to propagate the different errors using ? to the top and perhaps choose to deal with them or ignore them if I want.
I cannot do that with a std::result::Result<T, E> because I don't know the type of error returned (like I said, each crate could return its own errors).
I am aware that in Rust there is no "object-oriented" polymorphism, but there are trait objects. Since a trait object's size cannot be known at compile time, we must hide them behind some kind of pointer like & or Box<_>.
The base trait implemented by errors seems to be std::error::Error.
One thing I've seen is the fn foo() -> Result<Blah, Box<dyn Error>> strategy, which utilizes the concept of trait objects.
The problem with this strategy is none of the crates return a boxed error, which leads to the compiler complaining about the same.
An example use-case:
use native_tls::TlsConnector; // 0.2.3
use std::io::{Read, Write};
use std::net::TcpStream;
fn main() {
match do_stuff() {
Ok(string) => {
println!("{}", string);
}
_ => {
println!("Failed!");
}
}
}
fn do_stuff() -> Result<String, Box<(dyn std::error::Error + 'static)>> {
let connector = TlsConnector::new()?;
let stream = TcpStream::connect("jsonplaceholder.typicode.com:443")?;
let mut stream = connector.connect("jsonplaceholder.typicode.com", stream)?;
stream.write_all(b"GET /todos/1 HTTP/1.0\r\n\r\n")?;
let mut res = vec![];
stream.read_to_end(&mut res)?;
String::from_utf8(res)
}
playground
Is there an easy way around this problem? Can I easily abstract away all the different errors and return a Result so I can use the ? idiom?
Can you return a Result that works with any possible error type?
No, you cannot. On the surface, this cannot make sense. Generic types are chosen by the caller of the function, so how would a function create an error that was chosen by someone else, without being told how to construct it?
That said, your problem is easily solved. You said:
so I can use the ? idiom
If you do that consistently, your program compiles:
let s = String::from_utf8(res)?;
Ok(s)
You could also convert the error type directly:
String::from_utf8(res).map_err(Into::into)
none of the crates return a boxed error, which leads to the compiler complaining about the same
It does not for the 5 other cases where you've used ?, so it's unclear why you make this statement.
Specifically, Box<dyn Error> can be created from any type that implements Error:
impl<'a, E: Error + 'a> From<E> for Box<dyn Error + 'a> {
fn from(err: E) -> Box<dyn Error + 'a> {
Box::new(err)
}
}
The ? operator calls From::from for you under the hood.
See also:
What is this question mark operator about?
How to manually return a Result<(), Box<dyn Error>>?
Rust proper error handling (auto convert from one error type to another with question mark)

How does the ? operator interact with the From trait?

Say I have the following:
use std::fs::File;
impl From<i32> for Blah {
fn from(b:i32) -> Blah {
Blah {}
}
}
fn main() {}
enum MyError {
ParseError,
}
impl From<std::io::Error> for MyError {
fn from(_:std::io::Error) -> Self {
MyError::ParseError
}
}
fn get_result() -> Result<Blah, MyError> {
let mut file = File::create("foo.txt")?;
}
This compiles fine. I don't understand how.
File::create throws an std::io::error, which we're trying to wrap in a MyError. But we never explicitly call from anywhere!? How does it compile?
As the comments from this answer Rust understanding From trait indicate, you do have to explicitly call from.
So, how is the above snippet compiling?
The difference is stated in The Rust Programming Language, chapter 9, section 2, when talking about the ? operator:
Error values that have the ? operator called on them go through the from function, defined in the From trait in the standard library, which is used to convert errors from one type into another. When the ? operator calls the from function, the error type received is converted into the error type defined in the return type of the current function. This is useful when a function returns one error type to represent all the ways a function might fail, even if parts might fail for many different reasons. As long as each error type implements the from function to define how to convert itself to the returned error type, the ? operator takes care of the conversion automatically.
You have provided this implementation of From<std::io::Error> for that error type, therefore the code will work and convert values of this type automatically.
The magic is in the ? operator.
let mut file = File::create("foo.txt")?;
expands to something like (source)
let mut file = match File::create("foo.txt") {
Ok(t) => t,
Err(e) => return Err(e.into()),
};
This uses the Into trait, which is the counterpart to the From trait: e.into() is equivalent to T::from(e). Here you have the explicit conversion.
(There is an automatic impl<T, U> Into<U> for T for every impl<T, U> From<T> for U, which is why implementing From is enough.)

Rust `From` trait, errors, reference vs Box and `?` operator [duplicate]

This question already has answers here:
Is there any way to return a reference to a variable created in a function?
(5 answers)
Closed 3 years ago.
I am pretty confused on the ? operator in functions that return Result<T, E>.
I have the following snippet of code:
use std::error;
use std::fs;
fn foo(s: &str) -> Result<&str, Box<error::Error>> {
let result = fs::read_to_string(s)?;
return Ok(&result);
}
fn bar(s: &str) -> Result<&str, &dyn error::Error> {
// the trait `std::convert::From<std::io::Error>` is not implemented for `&dyn std::error::Error` (1)
let result = fs::read_to_string(s)?;
return Ok(&result);
}
fn main() {
println!("{}", foo("foo.txt").unwrap());
println!("{}", bar("bar.txt").unwrap());
}
As you might see from the above snippet, the ? operator works pretty well with the returned boxed error, but not with dynamic error references (error at (1)).
Is there any specific reason why it does not work? In my limited knowledge of Rust, it is more natural to return an error reference, rather than a boxed object: in the end, after returning rom the foo function, I expect deref coercion to work with it, so why not returning the error reference itself?
Look at this function signature:
fn bar(s: &str) -> Result<&str, &dyn error::Error> {
The error type is a reference, but a reference to what? Who owns the value being referenced? The value cannot be owned by the function itself because it would go out of scope and Rust, quite rightly, won't allow you to return the dangling reference. So the only alternative is that the error is the input string slice s, or some sub-slice of it. This is definitely not what you wanted.
Now, the error:
the trait `std::convert::From<std::io::Error>` is not implemented for `&dyn std::error::Error`
The trait isn't implemented, and it can't be. To see why, try to implement it by hand:
impl<'a> From<io::Error> for &'a dyn error::Error {
fn from(e: io::Error) -> &'a dyn error::Error {
// what can go here?
}
}
This method is impossible to implement, for exactly the same reason.
Why does it work for Box<dyn Error>? A Box allocates its data on the heap, but also owns that data and deallocates it when the box goes out of scope. This is completely different from references, where the owner is separate, and the reference is prevented from outliving the data by lifetime parameters in the types.
See also:
Is there any way to return a reference to a variable created in a function?
Although it is possible to cast the concrete type std::io::Error into dyn Error, it is not possible to return it as a reference because the "owned" value is being dropped/erased/removed at the end of the function, same goes to your String -> &str. The Box<error::Error> example works because an owned Error is created in the heap (Box<std::io::Error>) and the std has an implementation of Error for Box<T> (impl<T: Error> Error for Box<T>).
If you want to erase the concrete type and only work with the available methods of a trait, it is possible to use impl Trait.
use std::{error, fs};
fn foo(s: &str) -> Result<String, Box<dyn error::Error>> {
let result = fs::read_to_string(s)?;
Ok(result)
}
fn bar(s: &str) -> Result<String, impl error::Error> {
let result = match fs::read_to_string(s) {
Ok(x) => x,
Err(x) => return Err(x),
};
Ok(result)
}
fn main() {
println!("{}", foo("foo.txt").unwrap());
println!("{}", bar("bar.txt").unwrap());
}

How do I apply an explicit lifetime bound to a returned trait?

Returning an iterator from a function in Rust is an exercise of Sisyphean dimensions, but I am told it's possible to return one as a trait without quite so much pain. Unfortunately, it isn't working: apparently, I need an explicit lifetime bound? Which is apparently not the same thing as adding a lifetime parameter. Which means I have no idea how to do that.
Here's my (tiny, test) code:
fn main() {
let args = get_args();
for arg in args {
println!("{}", arg);
}
}
fn get_args() -> Iterator {
std::env::args().filter_map(|arg| arg.into_string().ok())
}
What is the appropriate way to make this actually work?
Edit: rust version rustc 1.0.0-nightly (00df3251f 2015-02-08 23:24:33 +0000)
You can't return a bare Iterator from a function, because it is a trait, thus not a sized type.
In your situation, you'll need to put the iterator object inside a box, in order to make it into a sized object that can be returned from the function.
To do so, you can change your code like this:
fn get_args() -> Box<Iterator<Item=String> + 'static> {
Box::new(std::env::args().filter_map(|arg| arg.into_string().ok()))
}
Here I've added a lifetime specifier 'static for the trait object, meaning that it is completely self-owned (a function taking no arguments will almost always return something valid for the 'static lifetime in this sense).
You also need the <Item=String> part to explicit the type of data yielded by your iterator. In this case: Strings.
In this specific case you can manage to return a concrete type from your get_args, like so:
fn get_args() -> FilterMap<Args, fn(OsString) -> Option<String>> {
fn arg_into_string(arg: OsString) -> Option<String> { arg.into_string().ok() }
args().filter_map(arg_into_string as fn(OsString) -> Option<String>)
}
basically this applies to all the cases where the closure you use in the iterator adapter (in your case filter_map) is not really a closure, in that it does not capture any environment, and it can be modeled by a plain old function.
In general, if you do need to return a type that does contain a closure, you will indeed need to box it and return a trait object. In your case:
fn get_args() -> Box<Iterator<Item=String> + 'static> {
Box::new(std::env::args().filter_map(|arg| arg.into_string().ok()))
}