Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
jyn514 opened this issue May 17, 2024 · 1 comment
Open

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

jyn514 opened this issue May 17, 2024 · 1 comment
Labels
A-borrow-checker Area: The borrow checker A-diagnostics Area: Messages for errors, warnings, and lints D-confusing Diagnostics: Confusing error or lint that should be reworked. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. fixed-by-polonius Compiling with `-Zpolonius` fixes this issue. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@jyn514
Copy link
Member

jyn514 commented May 17, 2024

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

@jyn514 jyn514 added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels May 17, 2024
@rustbot

This comment was marked as resolved.

@aDotInTheVoid aDotInTheVoid added the fixed-by-polonius Compiling with `-Zpolonius` fixes this issue. label May 17, 2024
@fmease fmease added A-borrow-checker Area: The borrow checker D-confusing Diagnostics: Confusing error or lint that should be reworked. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. labels May 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-borrow-checker Area: The borrow checker A-diagnostics Area: Messages for errors, warnings, and lints D-confusing Diagnostics: Confusing error or lint that should be reworked. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. fixed-by-polonius Compiling with `-Zpolonius` fixes this issue. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

4 participants