rust-lang / rust-lang/rust

Moving mutable borrows in/out of inferred types results in the compiler thinking they are moved as if they were owned values

Open
#129,694 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-borrow-checker C-discussion
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

Apologies for the slightly long title, but I'm not sure how to best summarize this issue. The problem is as follows: I have some code that uses a stack of sorts (VecDeque in my case, but the exact type isn't relevant) and pops data from it. It then does something with the data, and optionally pushes it back onto the stack. Crucially, the data is a mutable borrow of something, i.e. &mut Whatever. The code roughly looks like this:

let mut work = VecDeque::new();

...

while let Some((module, node)) = work.pop_front() {
    if pass.run(module, node) {
        resolved = true;
    } else {
        work.push_back((module, node));
    }
}

...

Here node is a &mut Something. The problem I'm running into is that type-checking this code results in the following error:

   --> compiler/src/type_check/expressions.rs:632:45
    |
628 |             while let Some((module, node)) = work.pop_front() {
    |                                     ---- move occurs because `node` has type `&mut hir::DefineConstant`, which does not implement the `Copy` trait
629 |                 if pass.run(module, node) {
    |                                     ---- value moved here
...
632 |                     work.push_back((module, node));
    |                                             ^^^^ value borrowed here after move
    |
note: consider changing this parameter type in method `run` to borrow instead if owning the value isn't necessary
   --> compiler/src/type_check/expressions.rs:659:15
    |
656 |     fn run(
    |        --- in this method
...
659 |         node: &mut hir::DefineConstant,
    |               ^^^^^^^^^^^^^^^^^^^^^^^^ this parameter takes ownership of the value

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

In other words, it seems that the compiler treats node as if it were an owned value and thus applies move semantics to it, but it's a borrow instead.

This issue can in fact be resolved by giving work an explicit type annotation like so:

let mut work: VecDeque<(ModuleId, &mut hir::DefineConstant)> = VecDeque::new();

This suggests the issue is potentially due to type inference not inferring the correct type or ownership.

A very easy way to reproduce this is the following snippet:

struct Person {}

fn main() {
    let mut work = Vec::new();

    while let Some(person) = work.pop() {
        run(person);
        work.push(person);
    }
}

fn run(_person: &mut Person) {}

I expected to see this happen: it should just work, because person is a borrow

Instead, this happened: the compiler produces the following error:

    Checking playground v0.1.0 (/var/home/yorickpeterse/Projects/rust/playground)
error[E0382]: borrow of moved value: `person`
  --> src/main.rs:8:19
   |
6  |     while let Some(person) = work.pop() {
   |                    ------ move occurs because `person` has type `&mut Person`, which does not implement the `Copy` trait
7  |         run(person);
   |             ------ value moved here
8  |         work.push(person);
   |                   ^^^^^^ value borrowed here after move
   |
note: consider changing this parameter type in function `run` to borrow instead if owning the value isn't necessary
  --> src/main.rs:12:17
   |
12 | fn run(_person: &mut Person) {}
   |    ---          ^^^^^^^^^^^ this parameter takes ownership of the value
   |    |
   |    in this function
help: consider cloning the value if the performance cost is acceptable
   |
7  |         run(person).clone();
   |                    ++++++++

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

Like with my actual code, one can prevent this error by defining work as let mut work: Vec<&mut Person> = Vec::new(); instead of relying on type inference.

Meta

rustc --version --verbose:

rustc 1.80.1 (3f5fd8dd4 2024-08-06)
binary: rustc
commit-hash: 3f5fd8dd41153bc5fdca9427e9e05be2c767ba23
commit-date: 2024-08-06
host: x86_64-unknown-linux-gnu
release: 1.80.1
LLVM version: 18.1.7

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the minimal Vec<&mut Person> reproduction and compare it with the explicitly typed version. Then inspect compiler/src/type_check/expressions.rs around the reported loop and run method to trace how the inferred element type is handled. Done means the unannotated examples compile with the borrow moved back into the collection, with regression coverage for the behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.