How can I avoid moving nested values?

Hm, I thought I understood it all, but then I bumped into this:

fn just_looking(s: &Option<String>) {
    println!("Is this Some? {}", s.is_some())
}

fn main() {
    let s = String::from("yo");
    just_looking(&Some(s));
    println!("{s}");
}

This does not compile because I supposedly move s:

   Compiling playground v0.0.1 (/playground)
error[E0382]: borrow of moved value: `s`
 --> src/main.rs:8:15
  |
6 |     let s = String::from("yo");
  |         - move occurs because `s` has type `String`, which does not implement the `Copy` trait
7 |     just_looking(&Some(s));
  |                        - value moved here
8 |     println!("{s}");
  |               ^^^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
  |
7 |     just_looking(&Some(s.clone()));
  |                         ++++++++

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

I don't understand - how is s being moved when just_looking is only taking a reference? Where is it moved to? Can't I access an &Option<String> without moving it? Thanks!

You need to use Option<&T> in function arguments instead of &Option<T>.

Call .as_ref() or .as_deref() on Options owning their data. It will transform &Option<String> to Option<&String> or Option<&str>, which allow you to destroy the Option and/or copy the reference from inside it.

4 Likes

When you write Some(s), you move s into a Some. It doesn’t matter that you then take a reference — in order to take a reference to an Option<String>, an Option<String> has to exist in memory, which requires the Option to own the String (or be None).

Generally, when in this type of situation, you should work with Option<&str> instead.

fn just_looking(s: Option<&str>) {
    println!("Is this Some? {}", s.is_some())
}

fn main() {
    let s = String::from("yo");
    just_looking(Some(&s));
    println!("{s}");
}

Then, when you do have an &Option<String>, you can use Option::as_ref() (for simple owned values) or Option::as_deref() (for String-to-str) to conveniently convert from that form to the Option<&T> form.

6 Likes

Thanks, but the example I gave was simplified from my real problem. A bit more similar to my problem would be:

struct A {}
struct B {}
struct C {}

enum ABC {
    A(A),
    B(B),
    C(C)
}

fn look_at_abc(abc: &ABC) { todo!() }

fn consume_a(a: A) { todo!() }

fn main() {
    let a = A {};
    look_at_abc(&ABC::A(a));
    consume_a(a);
}

which fails with

error[E0382]: use of moved value: `a`
  --> src/main.rs:18:15
   |
16 |     let a = A {};
   |         - move occurs because `a` has type `A`, which does not implement the `Copy` trait
17 |     look_at_abc(&ABC::A(a));
   |                         - value moved here
18 |     consume_a(a);
   |               ^ value used here after move
   |
note: if `A` implemented `Clone`, you could clone the value
  --> src/main.rs:1:1
   |
1  | struct A {}
   | ^^^^^^^^ consider implementing `Clone` for this type
...
17 |     look_at_abc(&ABC::A(a));
   |                         - you could clone this value

For more information about this error, try `rustc --explain E0382`.

I suppose I could create a temporary value and destructure it again, but it's really awkward. Any better options? Thanks!

Unfortunately, there is no general automatic solution to this problem. Manual solutions include writing a second enum for references:

enum AbcRef<'x> {
    A(&'x A),
    B(&'x B),
    C(&'x C)
}

impl ABC {
    fn as_abc_ref(&self) -> AbcRef<'_> {...}
}

Or you can make look_at_abc generic, if there is a reasonable way to define a trait that captures what you want out of ABC:

fn look_at_abc<T: Thing>(value: &T) {...}
trait Thing {...}
impl Thing for A {...}
impl Thing for B {...}
impl Thing for C {...}
impl Thing for ABC {...}
1 Like

Thanks! In my case, the abc is mostly used as a unique key to look up things in a map. I suppose I could implement some kind of hashing (but at least 128 bits or so to avoid collisions), where the hash is Copy.

For now, I might just go with this:

struct A {}
struct B {}
struct C {}

enum ABC {
    A(A),
    B(B),
    C(C)
}

fn look_at_abc(abc: &ABC) { println!("Looking at abc.") }

fn consume_a(a: A) { println!("Consuming a.") }

fn main() {
    let a = A {};
    let abc = ABC::A(a);
    look_at_abc(&abc);
    let ABC::A(a) = abc else { unreachable!() };
    consume_a(a);
}
1 Like

This is a known problem with the design of std hashmaps. It only lets you use the Borrow trait for lookups, and that trait is nearly useless.

You'll need to switch to hashbrown:

Maybe moo: