Hi!
Suppose I have an Option<T>
that I want to process by another function, iff there is some value.
The final result shall be an Option<T>
again!
If the other function takes a T
and returns an Option<T>
then that is easy:
let result = optional_value.and_then(other_function);
But what if the other function (not my function) happens to take a T but returns a Result<T,E>
???
Sure, I can do the following, but it seems a bit convoluted:
let result = optional_value.map(other_function).map(Result::ok).flatten();
Alternatively, I can do the following, but again not the nicest solution:
let result = optional_value.and_then(|val| other_function(val).ok());
Is there a better, more "idiomatic" solution?
What I'd need is an and_then()
that accepts Result<T,E>
and implicitly maps it to an Option<T>
.
Thank you!
jbe
January 7, 2023, 3:43pm
3
dEajL3kA:
Alternatively, I can do the following, but again not the nicest solution:
let result = optional_value.and_then(|val| other_function(val).ok());
I didn't find any nicer solution either, and it's probably how I would do it (to avoid the double wrapping). But I'm also curious if there's a better solution.
2 Likes
Merely a rearrangement and not necessarily nicer:
let result = optional_value.map(other_function).transpose().ok().flatten();
But if you're in an early-return situation you could do:
fn foo(optional_value: Option<String>) -> Option<String> {
other_function(optional_value?).ok()
}
fn bar(optional_value: Option<String>) -> Option<String> {
optional_value.map(other_function)?.ok()
}
Or with try
blocks (unstable):
let result = try { other_function(optional_value?).ok()? };
let result = try { optional_value.map(other_function)?.ok()? };
2 Likes
jbe
January 8, 2023, 11:24am
6
When I work a lot with errors and have a bunch of similar cases where I need treatment that in a certain way, I sometimes define extension traits for my needs (which I use locally inside a module or crate).
You could use that to define your own and_then_res_ok
method, for example:
trait OptionResultExt<T> {
fn and_then_res_ok<U, F, E>(self, op: F) -> Option<U>
where
F: FnOnce(T) -> Result<U, E>;
}
impl<T> OptionResultExt<T> for Option<T> {
fn and_then_res_ok<U, F, E>(self, op: F) -> Option<U>
where
F: FnOnce(T) -> Result<U, E>,
{
match self {
Some(t) => match op(t) {
Ok(u) => Some(u),
Err(_) => None,
}
None => None,
}
}
}
fn other_function(arg: i32) -> Result<i32, ()> {
Ok(arg / 2)
}
fn main() {
let optional_value = Some(9);
let result = optional_value.and_then_res_ok(other_function);
println!("result = {result:?}");
}
(Playground )
Output:
result = Some(4)
This might increase or reduce code readability though, depending on the perspective.
P.S.: Not sure what's the best name for such a method. Maybe and_then_ok
is better?
3 Likes
It’s only a small usability improvement, but the need to specify the type signature twice can be avoided using a macro for extension traits
1 Like
Your and_then_res_ok
function seems like exactly what I was looking for
Maybe we could call it just and_try()
?
Too bad that this apparently is not in the standard library. Seems like a standard use-case...
Unfortunately, I need an actual Option<T>
value, not early-return.
try
block looks like a great addition
jbe
January 8, 2023, 12:26pm
10
It might be possible to provide a general implementation (for any "early return" type) when the try_trait_v2
stabilizes, but not sure. These try
/?
-related traits always confuse me.
I should take some time to understand them better, but not sure if they will make their way into stable eventually.
system
Closed
April 8, 2023, 12:27pm
11
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.