Lifetime of Option::map's argument

2019-07-03 16:56发布

问题:

I placee a string into an Option and try to map over it, e.g. to trim the string:

fn main() {
    let s = "     i want to be trimmed    ".to_string();
    let s_opt = Some(s);

    let x = s_opt.map(|z| z.trim());
    //     let x = s_opt.map(|z| z.trim().to_string());

    println!("'{:?}'", x);
}

The compiler shows a lifetime error

error[E0597]: `z` does not live long enough
 --> src/main.rs:5:27
  |
5 |     let x = s_opt.map(|z| z.trim());
  |                           ^      - `z` dropped here while still borrowed
  |                           |
  |                           borrowed value does not live long enough
...
9 | }
  | - borrowed value needs to live until here

This is clear, since z is only defined in the closure and the argument is passed by value.

Since the original variable s lives for the entire block, shouldn't the compiler be able to figure out that z is actually s?

The only way I can get this to work is by adding to_string (see the commented line), but then I am creating a new string object.

Another solution I found is to make s_opt a type of Option<&String> (see the second code block), but since functions can't return this kind of type this is not really an option.

fn main() {
    let s = "     i want to be trimmed    ".to_string();
    let s_opt = Some(&s);

    let x = s_opt.map(|z| z.trim());
    println!("'{:?}'", x);
}

Is there something I've overlooked or wouldn't it be better if the default implementation of map would be similar to this?

fn my_map<'r, F>(o: &'r Option<String>, f: F) -> Option<&'r str>
where
    F: Fn(&'r String) -> &'r str,
{
    match *o {
        None => None,
        Some(ref x) => Some(f(x)),
    }
}

fn main() {
    let s = "     i want to be trimmed    ".to_string();
    let s_opt = Some(s);

    let x = my_map(&s_opt, |x| x.trim());
    println!("'{:?}'", x);
}

回答1:

The map function consumes iterated values so they do not exist after the call to the given closure anymore. You cannot return references to them.

The best solution would be in-place trim on String directly. Sadly there is none in the standard library currently.

Your second solution is also possible with a small change. Instead of &String you take a &str:

fn main() {
    let s = "text".to_string();
    let s_opt = Some(s.as_str());
    let x = s_opt.map(|z| z.trim());
    println!("{:?}", x);
}


回答2:

As noted, Option::map consumes the original value to produce the output value. This is the most flexible and efficient implementation as you can convert an Option<A> to an Option<B> without needing to clone the original value.

The solution in this case is to convert your Option<String> (really a &Option<String>) into an Option<&String> using Option::as_ref. Once you have an Option<&String>, you can consume it without losing ownership of the original Option<String>:

fn main() {
    let s = Some("     i want to be trimmed    ".to_string());

    let x = s.as_ref().map(|z| z.trim());

    println!("{:?}", x);
}


标签: rust