Can I turn off recursive unused function warnings?

If I have

fn main(){
    // a();
}
fn a() {
    b();
}
fn b() {
    c();
}
fn c() {
    
}

I get three warnings:

warning: function `a` is never used
 --> src/main.rs:4:4
  |
4 | fn a() {
  |    ^
  |
  = note: `#[warn(dead_code)]` on by default

warning: function `b` is never used
 --> src/main.rs:7:4
  |
7 | fn b() {
  |    ^

warning: function `c` is never used
  --> src/main.rs:10:4
   |
10 | fn c() {
   |    ^

The first one is useful, in case I forget to uncomment the call to a() or something.

The other two are just noise. They will go away as soon as the first one is fixed.

Can I turn them off? The compiler surely knows that those two are just a consequence of the first one, doesn't it?

4 Likes

I don't think there's any option for this, no.

Listing everything that's unused is not just noise, though: if it only mentioned a being unused, and you fixed this by deciding you don't need it any more and just deleting a, then the next build would then just show b as unused... you might have to compile lots of times in a row if you're just trying to remove all the dead code.

It's also not always the case that there is a "chain" with a single start point, since functions can call each other - if a calls b and vice versa but no other calls to a and b exist they are both unused, but neither is a consequence of the other.

1 Like

Compiler is not a thinking entity. It couldn't “know” anything.

Now, if we would assume that you meant “compiler have all the information needed to solve this NP-hard problem and because our tools are not slow enough and rust-analyzer doesn't use enough RAM we can add such ability”… then you are correct: it's possible. I'm not sure it's feasible though: when message includes dozen functions it's not too long and when hundreds functions are involved I would rather see large list than wait for a couple of hours till compiler will solve the problem and present me with five or ten “perfect” options.

P.S. Now, if you say “let it just report c function” then it would be easy, but IMNSO much less useful than what compiler does now.

I don't understand why this would be the vertex cover problem.

Yes, it depends a bit on how the compiler builds its call graph. If it does by following and backtracking function calls starting at main(), then yes, this would be hard. But isn't that much more complicated than just looking at every function in the code and determining which other function it calls? Then there would be an entry a() -> b() in the adjacency list and it would be trivial to report only those functions that are called zero times.

1 Like

There's an issue about it.

1 Like

The compiler finds unused things by starting from all the things that are by-definition used (like pub items in the root of the crate, or special functions like main in binary crates, etc) and reporting everything that is not reachable from one of those roots as unused.

If you only report functions that are called zero times from a simple adjacency list then in the case where functions call themselves or call each other mutually, you would not recognise those as being unused at all.

So, if you want to report a "minimal" set of unused things, you are effectively asking "what is the smallest set of roots I would need to add so that everything is reachable", which is much more complex and does not always have a unique solution.

3 Likes

(Emphasis mine.)

Very good point. Although I think I would still prefer that over the flood of warnings that is there now.

Maybe they should be separate lints, so you can turn off one and leave the other on. You could have only the simple version during development and then have the full check on CI.

procedures are verteces, edges are “A calls B” relations. It's literally trivial map from any graph to a program which would need to solve problem for that graph.

No, it doesn't. It's just literally the structure of call graph which maps to the vertex cover problem.

This just wouldn't work. You are not interested in functions that called, but in functions that are not called.

It's not much complicated if you don't care about speed or memory use, sure.

And what should be reported in case like this:

fn a() {
  b()
}

fn b() {
  a()
}

Nothing? Thanks, but no, thanks.

1 Like

During development I just keep all functions used by adding three letters pub to them (and appropriate comment if it's not development of one changeset but something that I plan to commit and develop for a long time).

Well, yes, actually. That's what I see as an "unused function", one that has no call written anywhere in the code. In your example neither a() nor b() are unused, even though they are never actually called in the application. If there was an if date == '2023-02-31' { a() } in the code, they would still never actually be called but the compiler wouldn't call them "unused functions" anymore.

They wouldn't be “unused” because someone may set date to '2023-02-31' and then function a() would be called.

In my example, though, both a and b are not used, they would be removed by linker and both would reported as unused by the compiler. Which is precisely what I want, obviously.

But it's not how “unused function” was defined by Turbo Pascal thirty years ago, it's not how “unused object file” was defined by C linker fifty years ago and I don't think it would be good idea to change that in Rust today.

1 Like

But it is how it works in PhpStorm with PHP, it is how it works in VS Code with C and IIRC it is also how it works in Visual Studio with C#. Much more important comparisons than some legacy toolchain from decades ago IMHO.

If any mention at all of a function meant it wasn't counted as dead_code, then every recursive function (or pair of mutually-recursive functions) would always be seen as used, and the compiler would never be able to tell you that (the code should perhaps be deleted | a call to it is missing), which is the entire point of the dead code lint, in the case of recursive functions.

I do agree that it would be nice to be able to at least distinguish “direct” dead code from “indirect” dead code, so one could have a cleaner report when one is not trying to delete code.

2 Likes

They are never called, that's exactly why they are unused.

I sense moving goalposts. That's simply because you are now painting a dynamic condition into the picture. That's not what unused code analysis does. Usedness is determined based on the statically-known call graph. The distinction is crystal clear.

2 Likes

Yes, all these tools couldn't traverse the graph and detect all the functions that are not used. In spite of the fact that linkers did the proper job more than half-century ago.

I wouldn't say so. When I found out that Rust actually reports about all unused functions I was overjoyed. No more sloppy, half-done work, lessons have been, finally, learned. I now know exactly how much unused code I have (not the case with sloppy non-recursive tracking).

You may like superfluous, sloppy, reporting better… fair enough, this is valid complaint and maybe should be a separate option (feel free to fill the bug in the issue tracker, it's not hard to implement and someone may do that).

But please don't remove an option to stop reporting about dead code: linkers did the job for decades while IDEs haven't done the same simply because our computers weren't powerful enough.

Today they are powerful enough and precise reporting is useful… at least for me.

1 Like

It would be nice if it were a bit less verbose, at least? I don't think it needs the code frame at least, and from memory it's hilariously verbose for a bunch of unused type fields (eg you just added a bunch of serde mapped types), it could simply list the names in one message or something.

It doesn't overly bother me though, I can always throw in a temporary #![allow(dead_code)]

For me it's exactly the other way around. Because I get all those warnings for indirectly unused functions, I have to fix the first one, compile, fix the next one, compile, etc. There's no way I can go through 20 warnings, each time finding out that it was just indirectly unused and there's nothing to fix.

2 Likes

Can you explain what layman-understandable task you are trying to fix here?

Because the only thing which I can imagine (I don't care about correctness of my program I just want to make compiler happy) is solved by removing all that code and doesn't need multiple passes.

If some of these functions are, in fact, supposed to be used (but are not used because of typo) then I couldn't imagine how they can you do that “fix the first one” trick… how would you “use it” if there are no place in your program to plug it in?

In the majority of cases I get "unused function" warnings because I commented something out temporarily and being able to see which functions have missing direct references is a great reminder that they are currently deactivated and I should check if I just forgot to uncomment them.

2 Likes

But that may only work in the case where each function is only use once! Otherwise you can uncomment it in one place, it would still be use “used” and you'll get not warnings.

Sure, one can imagine codebase where this specific mode is useful, but IMNSO at this point we are firmly into There are probably children out there holding down spacebar to stay warm in the winter! YOUR UPDATE MURDERS CHILDREN. territory.

2 Likes