Strange behavior of rust-analyzer in Rust 1.97.0

Hi,

I am exposing a strange behavior in rust-analyzer in VScode, that I'm experiencing since I upgraded Rust from 1.96.1 to 1.97.0: I'm receiving dead code warnings in two cases, through a project consisting of several thousands LOCs. The warnings are the following:

warning: associated function `new` is never used
  --> src\task\internal_task.rs:34:12
   |
33 | impl CommandRunner {
   | ------------------ associated function in this implementation
34 |     pub fn new() -> Self {
   |            ^^^
   |
   = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default

which is related to this piece of code:

// a command runner function type
type CommandRunnerFunction = fn(&str) -> Result<bool>;

// and the command runner structure to be instantiated only once
struct CommandRunner {
    command_runner: Option<CommandRunnerFunction>,
}

impl CommandRunner {
    pub fn new() -> Self {
        CommandRunner {
            command_runner: None,
        }
    }

    pub fn set_runner(&mut self, f: CommandRunnerFunction) {
        self.command_runner = Some(f);
    }
}

// an instance of the command runner that will be used by all tasks to run
// internal commands: it is synchronized in order to avoid collisions
lazy_static! {
    static ref COMMAND_RUNNER: Mutex<CommandRunner> = Mutex::new(CommandRunner::new());
}

(I pasted the entire context just to show that the function is actually used). The second warning is about a pub const:

warning: constant `APP_GUID` is never used
  --> src\constants.rs:21:11
   |
21 | pub const APP_GUID: &str = "663f98a9-a1ef-46ef-a7bc-bb2482f42440_DEBUG";
   |           ^^^^^^^^

Well, no need to waste space reporting where the const is defined, it is exactly where rust-analyzer says. It is used, however, in another module (main), this time too in a lazy_static! context:

    // single instance name
    static ref INSTANCE_GUID: String = format!(
        "{APP_NAME}-{}-{APP_GUID}",
        { if let Ok(s) = username() { s } else { String::from(STR_UNKNOWN_VALUE) }},
    );

I've started thinking that this might be part of the process of deprecating lazy_static!, but I've found that the related issue is not closed yet.

I tried to switch from the rust-analyzer that is provided with the extension, to the one that can be installed directly to the toolchain by using rustup component add rust-analyzer, with the same results.

This happens on two different VScode setups, one on Windows, and the other one on Linux.

This said, the code shows no warning by cargo checking directly, or by compiling it. And the problem popped up only after the above mentioned upgrade.

Anyone is experiencing something similar? I'd like to know, because I really wouldn't want to file an issue being the only affected person in the whole universe.

Thank you,

Francesco

I know that rust-analyzer sometimes skips things like items nested inside of items for certain analysis. Looking at what lazy_static expands into in a playground appears to reveal that the initialization expression gets put into a function item within another function… though IMO it would feel weird to ignore those for the purpose of unused-lints :thinking:[1] So all-in-all, I’d say this sounds plausibly behavior caused by rust-analyzer, and also plausibly unintional/buggy.

Not seeing a warning from doing cargo check directly can indeed point towards this being rust-analyzer-specific behavior. That’s especially the case if the warnings come up instantly, i.e. without needing to save to reload.[2] (I’m actually not quite sure what the most actionable way is to differentiate for sure between a diagnostic coming from rust-analyzer directly vs. one from rustc (through cargo).) A different source of discrepancies could always be e.g. the set of crate feature flags enabled.


If you want more confidence for/before creating an issue, then try to create a smaller reproducing use-case, i.e. just containing the parts from your “project consisting of several thousands LOCs” that actually matter to reproduce this behavior. This can also help others more easily confirm the issue.


  1. it’s more sensible for things like trait impls… but used/unused is supposed to look through basically all the bodies of functions anyway ↩︎

  2. e.g. if you added a second use-case for the thing being called “unused” and/or removed it again, do red squiggles and the warning appear/disappear without needing to save? ↩︎

Thank you indeed, I'll try to see if it's possible to reproduce the buggy behavior within a single source file, for instance: I'm not sure whether or not I'll be able to, as there are other parts within the same project which are very similar to the mentioned ones, also within lazy_static! contexts, and no warning is showing up in those cases.

I also thought that the enabled features could be discriminating, and had in fact fixed my Cargo.toml so that rust-analyzer would use the same defaults as cargo in debug mode before posting here.

(I didn't notice I wrote thousends in the original post... sorry for that :grimacing:, fixed it!)

F.

Nice! :grinning_face_with_smiling_eyes: I hadn't actually noticed that typo at all until you mentioned it now. I had just copy pasted the description because I didn't want to type it.

Probably I didn't notice because in German, “tausend(e)” would be with an “e” as well

rust-analyzer doesn't emit dead code warnings, so it must be cargo check. The reason it does not show up from the terminal is likely different flags being used; the first thing to try is cargo check --all-targets, but the best thing to do is to extract the command r-a uses from its logs - set RA_LOG=rust_analyzer::flycheck=debug (e.g. via rust-analyzer.server.extraEnv), extract the command (the logs in VSCode are in Output>rust-analyzer Language Server), and run it from the terminal.

Ok, I will try more extensive invocations of cargo check then, and I'll take a look at the log and report in. Thanks!

F.

@chrefr I tried the steps you proposed, and in fact the command and output are

❯ cargo "check" "--workspace" "--manifest-path" "Cargo.toml" "--keep-going" "--all-targets" "--features" "wmi dbus lua_sync lua_httpreq"
warning: associated function `new` is never used
  --> src\task\internal_task.rs:34:12
   |
33 | impl CommandRunner {
   | ------------------ associated function in this implementation
34 |     pub fn new() -> Self {
   |            ^^^
   |
   = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default

warning: constant `APP_GUID` is never used
  --> src\constants.rs:21:11
   |
21 | pub const APP_GUID: &str = "663f98a9-a1ef-46ef-a7bc-bb2482f42440_DEBUG";
   |           ^^^^^^^^

warning: `whenever` (bin "whenever" test) generated 2 warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.44s

(I just omitted the JSON format for readability). Actually, the --all-targets switch alone is enough for the warnings to appear. Theoretically, there should be one single target inferred by Cargo, as the layout has been created (by Cargo itself) for a binary project.

I'll try to reproduce the problem in a simpler context, as @steffahn suggested.

F.

Ok, got it...

The directory structure is the standard one: Cargo.toml is in the project main directory, and the Rust source files are in src.

The toy project just scraps the parts that I mentioned in the original post and their related dependencies, and the single files are:

  1. Cargo.toml
[package]
name = "raunused"
version = "0.1.0"
edition = "2024"

[dependencies]
lazy_static = "1.4"
parking_lot = "0.12"
  1. main.rs:
use lazy_static::lazy_static;

mod constants;
mod somelib;
use constants::*;
use somelib::*;

lazy_static! {
    static ref ANOTHER_CONST: String = format!(
        "{APP_NAME}-{}-{APP_GUID}",
        String::from("placeholder"),
    );
}

fn main() {
    let _ = set_sample_type_func(|_| { Ok(true) });
    println!("The string is: `{}`", ANOTHER_CONST.as_str());
}
  1. constants.rs:
pub const APP_NAME: &str = env!("CARGO_PKG_NAME");
pub const APP_GUID: &str = "some-GUID";
  1. somelib.rs:
use parking_lot::Mutex;
use lazy_static::lazy_static;
use std::io::Result;

type SampleTypeFunc = fn(&str) -> Result<bool>;

struct SampleType {
    sample_member: Option<SampleTypeFunc>,
}

impl SampleType {
    pub fn new() -> Self {
        SampleType {
            sample_member: None,
        }
    }

    pub fn set_func(&mut self, f: SampleTypeFunc) {
        self.sample_member = Some(f);
    }
}

lazy_static! {
    static ref SAMPLE_STATIC: Mutex<SampleType> = Mutex::new(SampleType::new());
}

pub fn set_sample_type_func(f: SampleTypeFunc) -> Result<()> {
    let mut item = SAMPLE_STATIC.lock();
    item.set_func(f);
    Ok(())
}

and the output from cargo check --all-targets is the following:

❯ cargo check --all-targets
warning: constant `APP_NAME` is never used
 --> src\constants.rs:1:11
  |
1 | pub const APP_NAME: &str = env!("CARGO_PKG_NAME");
  |           ^^^^^^^^
  |
  = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default

warning: constant `APP_GUID` is never used
 --> src\constants.rs:2:11
  |
2 | pub const APP_GUID: &str = "some-GUID";
  |           ^^^^^^^^

warning: associated function `new` is never used
  --> src\somelib.rs:12:12
   |
11 | impl SampleType {
   | --------------- associated function in this implementation
12 |     pub fn new() -> Self {
   |            ^^^

warning: `raunused` (bin "raunused" test) generated 3 warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.04s

while cargo run goes without warnings. Maybe there is a reason for this, or should I open an issue?

Thank you again,

F.

Look at the line where Cargo tells you what target produced the warning:

warning: `whenever` (bin "whenever" test) generated 2 warnings

That is, it's the target for the tests of the binary. That's what's not compiled by cargo run.

But I don't understand why you would be getting these warnings. It's true that the items are unused if you are running tests and therefore not running main(), but if that produced dead code warnings, then almost every binary target would see such warnings, which is not the case.

I can reproduce the warnings you see by running cargo check --all-targets with 1.97.0, but not with 1.98.0 beta or nightly. I would think this must be a compiler bug, but if so, why isn’t it bothering more people? Maybe it’s specific to the particular kind of code lazy_static! is creating.


As a workaround, you can add this to your Cargo.toml:

[[bin]]
name = "raunused"  # or whatever the name of your existing binary is
test = false

This will exclude the bin test target from being counted in --all-targets (it will only be compiled if you ask for testing that binary in particular).

I knew I had to look more thoroughly, thank you for pointing it out!

If the problem is not affecting 1.98.0 nightly, then maybe it is something that has already been spotted, therefore there's probably no need to file an issue. I think as well that it may depend on the lazy_static! implementation. For now, the idea of excluding the test target should do the job, at least until the problem has been fixed.

Thank you all for helping!

F.

Well, it didn't work unfortunately... Looks like --all-targets just checks all targets, regardless of what you exclude in the [[bin]] table. However, my original problem was with VScode and rust-analyzer, so for now I disabled --all-targets in the editor settings, and this stops the annoying warnings for good.

Nevertheless I hope that, if this is actually a bug, it will be fixed in the next release, as your experience with the nightly toolchain seems to suggest.

I looked at recent issues and PRs and I wonder if maybe Keep rename-imported main alive in dead-code analysis under `--test` by MaximilianAzendorf · Pull Request #157646 · rust-lang/rust · GitHub accidentally fixed this issue, since it changes the interaction of dead code analysis and main.

Maybe, it seems in fact to modify "deadness" checks in test targets. Strange that I didn't find the corresponding issue, since I was looking for issues concerning dead code in general. And the bug seems to affect Rust 1.96.0, which gave me no warnings.

I should investigate a little on this, maybe it's just the default configuration of the rust-analyzer extension in VScode that changed in the most recent release, to set the --all-targets flag. I tend to be lazy and use defaults almost everywhere...

As stated above, it's actually not rust-analyzer being wrong here - it can either be a Cargo bug, a rustc bug or a bug in how the user invokes them - but rust-analyzer is definitely fine.