rust-lang / rust-lang/rust

fixed-by-polonius loops/functions have very bad diagnostics

Open
#125,217 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-borrow-checker A-diagnostics D-confusing D-terse fixed-by-polonius T-compiler
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

Code
struct Command(Vec<String>);
impl Command {
    fn find_subcommand_mut(&mut self, _name: &str) -> Option<&mut Self> {
        None
    }
}

fn foo(app: &mut Command) {
    let alias_definition = vec!["submodule", "status"];
    let mut cmd = app;
    for arg in alias_definition {
        if let Some(next) = cmd.find_subcommand_mut(arg) {
            cmd = next;
        } else {
            break;
        }
    }
    dbg!(&cmd.0);
}
Current output
error[E0502]: cannot borrow `cmd.0` as immutable because it is also borrowed as mutable
  --> src/lib.rs:18:10
   |
12 |         if let Some(next) = cmd.find_subcommand_mut(arg) {
   |                             --- mutable borrow occurs here
...
18 |     dbg!(&cmd.0);
   |          ^^^^^^
   |          |
   |          immutable borrow occurs here
   |          mutable borrow later used here
Desired output
"use -Zpolonius lol"
Rationale and extra context

"immutable borrow used here" pointing at the same span as "mutable borrow used here" is very unhelpful. the only case this could possibly make sense is when the code is in a loop, which this is not.

i originally ran into this problem inside of an (unrelated) loop, making the diagnostic even more confusing.

Other cases

removing break makes the error a little better, but it still would be nice to say "this is a limitation of lifetimes" instead of "your code is broken"

fn foo(app: &mut Command) {
    let alias_definition = vec!["submodule", "status"];
    let mut cmd = app;
    for arg in alias_definition {
        if let Some(next) = cmd.find_subcommand_mut(arg) {
            cmd = next;
        }
    }
}
error[E0499]: cannot borrow `*cmd` as mutable more than once at a time
  --> src/lib.rs:12:29
   |
12 |         if let Some(next) = cmd.find_subcommand_mut(arg) {
   |                             ^^^
   |                             |
   |                             `*cmd` was mutably borrowed here in the previous iteration of the loop
   |                             first borrow used here, in later iteration of loop

the recursive case is also pretty confusing, although it looks more like a "traditional" polonius error so maybe it's easier to recognize

use clap::Command; // 4.5.4

fn find_nested_cmd<'a, 'b, 'c>(cmd: &'a mut Command, query: &'b [&'c str]) -> (&'a mut Command, &'b [&'c str]) {
    // let next = query.first().and_then(|arg| cmd.find_subcommand_mut(arg));
    if let Some(arg) = query.first() {
        if let Some(next) = cmd.find_subcommand_mut(arg) {
            return find_nested_cmd(next, &query[1..]);
        }
    }
    (cmd, query)
}

fn foo(app: &mut Command) {
    let alias_definition = vec!["submodule", "status"];
    let (subcmd, args) = find_nested_cmd(app, &alias_definition);
    dbg!(subcmd.get_bin_name());
}
error[E0499]: cannot borrow `*cmd` as mutable more than once at a time
  --> src/lib.rs:10:6
   |
3  | fn find_nested_cmd<'a, 'b, 'c>(cmd: &'a mut Command, query: &'b [&'c str]) -> (&'a mut Command, &'b [&'c str]) {
   |                    -- lifetime `'a` defined here
...
6  |         if let Some(next) = cmd.find_subcommand_mut(arg) {
   |                             --- first mutable borrow occurs here
7  |             return find_nested_cmd(next, &query[1..]);
   |                    ---------------------------------- returning this value requires that `*cmd` is borrowed for `'a`
...
10 |     (cmd, query)
   |      ^^^ second mutable borrow occurs here

another workaround is to add an extra unwrap() so it doesn't look to the compiler like the lifetimes are related:

    for arg in alias_definition {
        if let Some(next) = cmd.find_subcommand_mut(arg) {
            cmd = cmd.find_subcommand_mut(arg).unwrap();
        }
    }
Rust Version
rustc 1.80.0-nightly (8c127df75 2024-05-16)
binary: rustc
commit-hash: 8c127df75fde3d5ad8ef9af664962a7676288b52
commit-date: 2024-05-16
host: x86_64-unknown-linux-gnu
release: 1.80.0-nightly
LLVM version: 18.1.4
Anything else?

@rustbot label fixed-by-polonius

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 reproducing the loop and recursive examples with the cited Rust nightly version and compare their current borrow-checker diagnostics. Investigate the Polonius-related diagnostic path responsible for these messages. Done means the examples explain the lifetime limitation clearly instead of pointing to misleading later-use spans, with coverage for the reported cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.