Why does map takes ownership of Option in Option<T> but not in Option<&T>

I have something that looks like this:

    let greet: Option<String> = Some("hi".to_string());

    let mapped = greet.map(|e|e);

    dbg!(mapped);
    dbg!(greet);

But fails to compile with the error

use of moved value: `greet` [E0382] value used here after move Note: `std::option::Option::<T>::map` takes ownership of the receiver `self`, which moves `greet` Help: you can `clone` the value and consume it, but this might not be your desired behavior

Then I used as_ref to get Option<&String> from Option<String>

    let greet: Option<String> = Some("hi".to_string());

    let mapped = greet.as_ref().map(|e|e);

    dbg!(mapped);
    dbg!(greet);

This compiles fine which I was not expecting.

If map takes ownership of the receiver self why does it take ownership of Option when it contains a String but not when it contains &String. I would have assumed the ownership of Option will be moved regardless of what it contains.

I think this is because Option<&T> implements Copy.

fn main() {
    let _a = Some("Hello"); // works
    //let _a = Some("Hello".to_string()); // fails
    let _b = _a;
    let _c = _a;
    drop(_a);
}

(Playground)

3 Likes

And that is the case. You are probably mixing up the types. When you have an Option<&String>, then T = &String (and not, for example, String). So the .map() call takes ownership of the reference to the string, because that's what is inside the Option. And of course, consuming a reference does not in itself invalidate the referred value. (This is not symmetric – consuming a value does invalidate all references to it.)

3 Likes

I think in

let mapped = greet.as_ref().map(|e|e);

the map method takes ownership of a reference to an Option<&String> (in the first place) instead of taking ownership of a &String. Either implement Copy though.

1 Like

Well, but it moves the &String transitively (and that's what we care about), because the Option::<&String>::Some contains a &String. This doesn't contradict what I wrote.

Well, implicitly, the inner &String is copied as well, yes.

There’s two aspects here, better demonstrated if we split up the .as_ref() and the .map(…) step

fn main() {
    let greet: Option<String> = Some("hi".to_string());

    let as_ref = greet.as_ref();
    let mapped = as_ref.map(|e| e);

    dbg!(&mapped, &as_ref, &greet); // all 3 still exist
}

The reason why greet still exists is because the .as_ref method only takes a &self argument of the original Option, and from that produces a new option (of type Option<&String> in this case). Even if that newly produced value was moved, it wouldn’t do anything to the original Option. The reason why even as_ref, too, still exists despite the call to map is because Option<&String> implements Copy.

The original Option is notable not entirely out of the game here. It’s still borrowed. Since the mapped operation kept the reference as a reference, both as_ref and mapped are still considered to borrow from greet, and something like

fn main() {
    let greet: Option<String> = Some("hi".to_string());

    let as_ref = greet.as_ref();
    let mapped = as_ref.map(|e|e);

    drop(greet);
    dbg!(&mapped);
}

thus fails (same thing for trying dbg!(as_ref) instead).

error[E0505]: cannot move out of `greet` because it is borrowed
 --> src/main.rs:7:10
  |
4 |     let as_ref = greet.as_ref();
  |                  -------------- borrow of `greet` occurs here
...
7 |     drop(greet);
  |          ^^^^^ move out of `greet` occurs here
8 |     dbg!(&mapped);
  |          ------- borrow later used here

If on the other hand the mapped operation would create an owned value, e.g. something like .map(|e| e.len()), then mapped would become completely independent of the original greet. E.g. this compiles fine

fn main() {
    let greet: Option<String> = Some("hi".to_string());

    let as_ref = greet.as_ref();
    let mapped = as_ref.map(|e| e.len());

    drop(greet);
    dbg!(&mapped); // but using `&as_ref` here instead would still error
}
2 Likes

This was the missing piece for me. Thanks for pointing this 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.