Using eager 'or' and 'and' methods on Result

I've got question around the usage of eager methods like and and or on Result:

fn and<U>(self, res: Result<U, E>) -> Result<U, E>
fn or<F>(self, res: Result<T, F>) -> Result<T, F>

Given they are eager, it stands to reason that they should only be used with constants and precomputed values for the parameter res. Is this correct or am I missing missing something?

Given that, how useful are these methods in practice over their lazy counterparts: and_then and or_else?

I'd be keen to see some real world examples of how these methods are used. Are there any specific use cases where these eager methods shine through over the lazy variants? I'm struggling to think of cases where this would be true.

Note: I've cross posted about this on the Rust Discord as well. I'm hoping for some good examples of where this would be useful.

Thanks

Here's one quick example for capturing errors and converting them to the application's error type.

There are a lot of potential uses for .or() and the other similar methods of Result. For an error type where the messages they contained are static strings, .or() would be appropriate. If an error variant contains a string dynamically created, .or_else() would be better.

In pratice, personally, I find myself using the lazy counterparts of such methods more often. But there are some cases where the eager methods are better, where computation overhead is minimal or the values are static.

There are so many features, methods, types, etc. in Rust it's difficult, if not impossible, to always have an idea how they could be applied. Just make a mental note that they're there, and as you write more Rust code, eventually you'll run into cases where it suddenly makes sense to apply them.

I'd much rather read and write

some_res.and(Ok(some_const))

than

some_res.and_then(|| Ok(some_const))

as unnecessary closures add nothing but line noise and optimizer overhead. There's no reason to use a lazy variant when the value on the right is trivial.

Thanks for the explanation. :slightly_smiling_face:

In this example:

    s.split_whitespace()
     .map(|s| s.parse())
     .collect::<Result<Vec<i32>, _>>()
     
     // One example usage.
     .or(Err(ConversionError("Bad number string passed to convert().")))

I am right in saying that Err and Conversion instances are created when the parsing fails and when it succeeds?

I presume, in this instance, you don't care too much about the instance creation cost (which is low) as it's more readable?

In this specific example, there's no runtime creation cost. What's happening here is an enum variant ConversionError and its static &str are resolved at compile time. So when this code runs, if the .collect() succeeds, it's Result object is returned directly, and if it fails the precompiled error is returned.

3 Likes

True, this fine if you have constants or pre-computed Results. Is this the primary use for or and and?

I guess you could use the eagerness to do something weird like run a function on both Ok and Err as long as the return types match.

fn main() {

  let p = pass();
  let f = fail();
  
  p.and(log("success")).unwrap();
  f.and(log("failure")).unwrap_or(());
}

fn log(message: &str) -> Result<(), String> {
    Ok(println!("log: {message}"))
}

fn fail() -> Result<(), String> {
    Err("Failed".to_owned())
} 

fn pass() -> Result<(), String> {
    Ok(())
} 

which yields:

log: success
log: failure

In this case, the log() methods are going to run no matter what. The compiler assumes you want the return value (probably ()) passed as the parameter after invoking them. In this case, .and_then(|| log("")) is better.

Yes, I prefer to use and_then. I was just trying to come up with weird use cases for and. :slightly_smiling_face:

I don't think I've ever personally used .and(), and I have written a lot of Rust code. I chalk this up to each of us having different styles and habits. There are a lot of things I don't find useful, but I'm sure others have good reasons for having such features available. If there's a use for .and(), I guess I'll know it when I need it.

I haven't see any. But I've used or quite often and it would be really weird to have or, or_then plus and_then but not and.

Worth having it even if just to not ask questions why it doesn't exist.

Yeah. "Symmetry" is the rationale behind some of the feature requests / bug reports I've seen. "We have list.sort(), but we don't have list.unsort(), we should add that" :blush:

Thanks!

TBH, and and and_then immediately became much less useful once ? existed. If you're in code handling results, it's most common that you're in a function returning a result too, and thus instead of a.and_then(|| b) it's more common that you'd just write a?; b instead.

or(_else) is certainly more useful, especially on Option, but I think I mostly use unwrap_or(_else) because if I'm doing it I often didn't want an Option out.

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.