Should I be using and_then() like this?


#1

Hi, I found myself using this pattern in my UI code (for a game I’m working on), and this pattern feels weird.

Basically I’m using and_then() method on an option to avoid manually unwrapping it and using the value inside, doing nothing if it is not present.

My question is, at the end of the closure (and_then()), I’m not sure what to return. Right now I’ve just been repacking the value I’m iterating and using it as the return value. From the documentation for and_then(), in the examples it’s obvious what to return. In their examples, their functions are doing something related to the data.

In my case, I’m just trying to do something if a value’s present. Maybe it would be more idiomatic to just to test the option for a value, if there is one, unwrap it and use it directly.

use cases:

https://github.com/bjadamson/softland/blob/master/src/ui.rs#L115-L122
https://github.com/bjadamson/softland/blob/master/src/ui.rs#L165-L181
https://github.com/bjadamson/softland/blob/master/src/ui.rs#L185-L196
https://github.com/bjadamson/softland/blob/master/src/ui.rs#L301-L308

Summary
I was trying to use Options’s and_then() function to avoid manually testing and unwrapping the option, but returning a value inside the and_then() closure has me questioning if using and_then() is the right function for my use case. Is there perhaps some better way to do this?


#2

You are looking for if let maybe?

if let Some(channel) = chat_history.lookup_channel(id) {
    let text = channel_name.to_owned();
    let text = unsafe { ImString::from_string_unchecked(text) };
    ui.text_colored(channel.text_color, &text);
};

#3

The canonical way to do the “if present, take the value and do something with it” is via if let Some(x) = <something_that_yields_an_option> { // use x here}

But I guess you don’t like this method since it’s a bit more long winded?


#4

This should probably be:

if let Some(ref channel) = chat_history.lookup_channel(id) {
    let text = channel_name.to_owned();
    let text = unsafe { ImString::from_string_unchecked(text) };
    ui.text_colored(channel.text_color, &text);
};

To avoid unwrapping the Result, because that seems what the code will do later.


#5

It seems like lookup_channel returns Option<&Channel> so channel binding is already a reference. Or did I misunderstand?


#6

That’s not the issue here.

if let Some(x) = my_result {

}

Consumes the result and moves it.

if let Some(ref x) = my_result {

}

Passes you a reference to the inner value and keeps the outer value intact. In this case, the reference would most likely be &&Channel, but that get’s optimised away.

To make matters more fun, there’s also Result#as_ref, which returns a new result with references to the old result and also makes the result reusable by operating on copies.

if let Some(x) = my_result.as_ref {

}

The difference can be seen in these three examples:

https://is.gd/sWeMFb
https://is.gd/IbgivE
https://is.gd/1wdjVQ


#7

The code in question was returning an rvalue so there’s nothing to reuse. Also, the more appropriate example is if let Some(x) = r.as_ref() {...} (note the as_ref). I’m also not sure why you’re talking about Result :slight_smile:

Edit: just noticed your as_ref() example - that’s exactly the scenario here, and it works just fine with let Some(x).


#8

Thank you everyone, if let Some(…) is exactly what I am looking for. :smile: