4

I place 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);
}
E_net4
  • 27,810
  • 13
  • 101
  • 139
wiep
  • 172
  • 1
  • 7

2 Answers2

3

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);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
PEPP
  • 917
  • 9
  • 7
  • Thank you PEPP for your reply. I actually got the problem while using std::io. There the returned type is `IoResult` (for example read_line). Thus the second solution cannot be used. As a consequence I have to use the `as_slice().trim().to_string()` solution or use the try! macro and wrap it in an extra function. Am I right? – wiep Sep 26 '14 at 14:47
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);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366