Reputation: 625
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?
Upvotes: 1
Views: 1163
Reputation: 171
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(())
}
Upvotes: 1
Reputation: 42302
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.
Upvotes: 0