3
votes

I expected the two functions below to be equivalent. However the first one does not compile.

pub fn does_not_work<I: IntoIterator>(values: I)
where
    I::Item: AsRef<str>,
{
    if let Some(value) = values.into_iter().nth(0).map(|item| item.as_ref()) {
        if value == "first" {
            println!("This should print");
        }
    }
}

pub fn does_work<I: IntoIterator>(values: I)
where
    I::Item: AsRef<str>,
{
    if let Some(value) = values.into_iter().nth(0) {
        if value.as_ref() == "first" {
            println!("This should print");
        }
    }
}

fn main() {
    does_work(&["first"]);
}

The compile error is:

error[E0597]: `item` does not live long enough
 --> src/main.rs:5:63
  |
5 |     if let Some(value) = values.into_iter().nth(0).map(|item| item.as_ref()) {
  |                                                               ^^^^        - `item` dropped here while still borrowed
  |                                                               |
  |                                                               borrowed value does not live long enough
...
9 |     }
  |     - borrowed value needs to live until here

The code is altered so as to be less verbose than the actual context it comes from and to more clearly illustrate the point. To clarify why I want to use the first approach, I use value many more times in my actual code and I don't want to have every single one of them followed by a .as_ref().

Is there a way to make this work or is Option::map not a good choice for this situation? Is there another concise way to solve this problem?

2

2 Answers

4
votes

When you create a new iterator, old values are not available anymore. But you need the old value to exist in order to return Some(value). In your case, you're passing a &[&'static str] to the function, so it's guaranteed to stay around long enough, but according to the types you could just as well pass &[String].

In that case, the original String could be freed and you'd be left with a dangling pointer. By not calling .as_ref(), you guarantee that the original value will be available in the Some(value).

If you just want to skip multiple .as_ref() calls you can instead do:

pub fn does_work<I: IntoIterator>(values: I)
where
    I::Item: AsRef<str>,
{
    if let Some(value) = values.into_iter().next() {
        let s = value.as_ref();
        if s == "first" {
            println!("This should print");
        }
    }
}
2
votes

That's because map takes ownership of the parameter item, so it will be destroyed after it returns. This makes the result reference invalid.

You should apply Option::as_ref to transform Option<T> into Option<&T> before using map, like this:

pub fn does_not_work<I: IntoIterator>(values: I)
where
    I::Item: AsRef<str>,
{
    if let Some(value) = values.into_iter().next().as_ref().map(|item| item.as_ref()) {
        if value == "first" {
            println!("This should print");
        }
    }
}