Search code examples
rustvectorcloneborrow-checkerownership

Why can't I move out of a shared reference to an owned vec which is not used afterwards?


I'm wondering why it is not possible to move out of a shared reference to a vector if the vector is owned and not used afterwards?

I have some code that looks like this:

#[derive(Debug)]
struct Item(usize);

fn main() {
    let items = vec!(Item(1), Item(2), Item(3), Item(4));
    let num: Item = handle_selection_and_return(items);
    dbg!(num);
}

/// Item is selected here, e.g. via UI.
fn select(items: &[Item]) -> &Item {
    return &items[1];
}

fn handle_selection_and_return(items: Vec<Item>) -> Item {
    // Additional code that may add additional items.
    // ...

    // Now select the item and return it.
    *select(&items)
}

This will lead to the following error:

error[E0507]: cannot move out of a shared reference
  --> src/main.rs:20:5
   |
20 |     *select(&items)
   |     ^^^^^^^^^^^^^^^ move occurs because value has type `Item`, which does not implement the `Copy` trait
   |
note: if `Item` implemented `Clone`, you could clone the value
  --> src/main.rs:2:1
   |
2  | struct Item(usize);
   | ^^^^^^^^^^^ consider implementing `Clone` for this type
...
20 |     *select(&items)
   |     --------------- you could clone this value

For more information about this error, try `rustc --explain E0507`.
error: could not compile `playground` (bin "playground") due to 1 previous error

Link to playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=4f7fdec2bede11ddc81031412aef26f4

I don't see why the item needs to be cloned here, because the vector is not used afterwards. Is there some way to avoid the unnecessary clone?


Solution

  • There are at least a couple different ways to look at this:

    • What it would mean for language design
    • What it would mean for runtime behavior

    Language design

    Firstly, the language semantics perspective. What you're essentially asking is something like: "if I have a function that takes a reference and returns a reference, why doesn't Rust automatically promote that to taking and returning an owned value when the call site of that function passes a reference to an object it owns and is about to drop".

    Even if the language could do that (see below), is it really worth the added complexity? Languages with implicit actions like that can quickly be very hard to understand: each action on its own sounds neat when you read about it in the documentation, but then you see a line that combines two or three of them in some very precise way, and it can quickly lead to "I don't actually understand what this code is doing."

    It's far easier for a human to understand if you create a select_owned(Vec<Item>) -> Item and just use that.

    Runtime behavior: where does the Item live?

    Secondly, the memory perspective: the Items in the Vec live in the heap, but a local Item lives on the stack. When you drop the Vec, its heap memory will be deallocated. What would you expect to happen to that stack Item's value, if it had somehow magically been "redirected" to the (no longer valid) heap-allocated Item?

    As soon as the Vec is dropped, its Items no longer exist. If you want to keep using one past that lifetime, you'll need to copy it to the new location (in this case, the stack).

    Your two standard options for that are .clone() or copy, the latter being what you're trying to do -- but that requires that the type implement Copy, which it doesn't. (Note that in your example, you can easily add the derive for Copy; I don't know if that's the case in your real code.)