3

Why does snippet pattern_matching_2 and pattern_matching_3 work whereas pattern_matching_1 doesn't? The compiler suggests that let &x = &foo moves the string hello, which I'm aware of and don't see where the problem is -- I'm not using foo anyway, and compiler doesn't complain anything about pattern_matching_3, which moves the string hello as well.

The example snippets are adapted from this answer, which didn't answer my question.

The code snippets:

fn pattern_matching_1() {
    let foo = String::from("hello");
    let &x = &foo;
    println!("{}", x);
}

fn pattern_matching_2() {
    let foo = 12;
    let &x = &foo;
    println!("{}", x);
}

fn pattern_matching_3() {
    let foo = String::from("hello");
    let x = foo;
    println!("{}", x);
}

The compiler error of pattern_matching_1:

error[E0507]: cannot move out of a shared reference
  --> src/main.rs:26:14
   |
26 |     let &x = &foo;
   |         --   ^^^^
   |         ||
   |         |data moved here
   |         |move occurs because `x` has type `String`, which does not implement the `Copy` trait
   |         help: consider removing the `&`: `x`
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Kevin
  • 143
  • 1
  • 7

2 Answers2

1

he compiler suggests that let &x = &foo moves the string hello, which I'm aware of and don't see where the problem is

The problem is that you've given the compiler an immutable reference to a variable (&foo) and then asked it to move away the underlying data. Which is not an operation that is permitted by immutable references.

To make this more explicit, extract the moving-out part into another function:

fn pattern_matching_1_helper(r: &String) {
    let &x = r;
    println!("{}", x);
}

fn pattern_matching_1() {
    let foo = String::from("hello");
    pattern_matching_1_helper(&foo);
}

I hope it's obvious that pattern_matching_1_helper cannot compile on its own. But the combined version in your code is really no different.

pattern_matching_2 compiles because i32 is Copy, so the compiler doesn't need to move.

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • I don't quite agree that the original version is no different. In your example, it's clear that `r` lives for the whole function, so we can't move out the underlying data. In the original example, the reference is a temporary that only lives for the duration of the statement, so moving out the underlying data wouldn't create any dangling reference in the original case, and the original code is, in fact, safe (albeit pointless). – Sven Marnach Mar 01 '21 at 10:12
  • To clarify, I know that your code is the same _from the viewpoint of the borrow checker_ – the reference lives until the end of the statement, so you can't move the data out during the statement. However, the reason for this isn't that the case are "no different", but that someone decided that this is how it works. – Sven Marnach Mar 01 '21 at 10:15
  • @SvenMarnach The lifetime of the reference is not relevant. What is relevant is that the compiler will not move a value through a reference. You can move from a variable. You **can never** move through a reference. – Sebastian Redl Mar 01 '21 at 10:25
  • Let me try to rephrase to clarify my point. Rust has the fundamental rule that references can never become invalid. As a consequence, the code in this answer is invalid: After moving the string, `r` would be an invalid reference. The code in the original post doesn't have that problem, though – even if Rust allowed moving the string in that case, we wouldn't end up with any invalid references. (And I'm not only splitting hairs here – I think this distinction is actually the cause of the confusion here.) – Sven Marnach Mar 01 '21 at 13:27
  • Sorry I failed to understand that "then asked it to move away the underlying data" is a problem. I no longer use the moved data (`foo`) after all. Why is this a problem? Thanks – Kevin Mar 02 '21 at 08:11
  • @SvenMarnach It's not about references becoming invalid. It's about what you're allowed to do with a reference. If you do `&variable`, you get a shared reference, a `&T` (or `&String` in this particular case). And you simply cannot use a shared reference to move away the underlying data. The reference is shared, there might be another reference pointing to the same data. *Could* the compiler figure out in this particular case that there isn't, and that the referenced variable is no longer used? Yes it could, but **why would it**? In 99.9% of real-world use cases, it would be too complicated. – Sebastian Redl Mar 02 '21 at 08:55
  • So it's simply not worth the specification and implementation effort to make this particular case work, when the solution for the programmer is to **simplify the code** by turning it into version 3 of the original question. – Sebastian Redl Mar 02 '21 at 08:56
0
fn pattern_matching_3() {
    let foo = String::from("hello");
    let x = foo;
    println!("{}", x);
}

This works because foo is moved into x, so foo is no longer a usable variable. It's ok to move data from the variable that owns it, as long as that variable is never used again.

fn pattern_matching_1() {
    let foo = String::from("hello");
    let &x = &foo;
    println!("{}", x);
}

This doesn't work because foo is still the owner of the string but you are trying to move the data out of x, which has only borrowed it.

pattern_matching_2 works because i32 is Copy, so nothing is moved.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • I didn't quite understand what you mean by "but you are trying to move the data out of `x`". As per [the book](https://doc.rust-lang.org/book/ch18-01-all-the-places-for-patterns.html#let-statements), `let PATTERN = EXPRESSION;`, `let x = foo;`should be equivalent to `let &x = &foo;`. Why is `let &x = &foo;` false? – Kevin Mar 02 '21 at 03:11