Unused variables and todo!()

Hello,

I've seen discussions about this, but no conclusions. Suppose I have some unfinished code and the place where work will continue is marked with a todo!(). Then I also have some variables that will be used once the todo!() is resolved, but now they are unused and cause warnings. I would like to avoid such warnings to distinguish from the case where I actually forgot about the variable.

For example, I stub a function:

fn foo(a: &A, b: &B, c: &C) -> Result<R, Error> {
  todo!()
}

This will warn me that a, b, and c are unused - well, duh, of course they are. But this also can happen without function stubs, like:

let a = ...;
let b = ...;
let c = ...;
todo!()  //  I'm planning to use a, b and c here

I can disable the warnings by using underscores or macros, but then I might actually forget about these variables and never use them and get no warning for that.

Ideally, I want no warning about not using those variables until I think I'm done, i.e. until I'm removing the todo!().

The most effective way that comes to my mind is adding the variables that are intended to be used as arguments to the todo!(), something like

fn foo(a: &A, b: &B, c: &C) -> Result<R, Error {
  todo!(a, b, c)
}

or

let a = ...;
let b = ...;
let c = ...;
todo!(a, b, c)  //  I'm planning to use a, b and c here

Currently, todo!() accepts arguments - but only a format string followed by expressions that must implement Display.

I found the crate todo_using that provides what I want, but how about we put this in std so that we don't need another dependency and so that there is one idiomatic way to do it?

12 Likes

The best way to get a convenience feature in std is to promote using the library so that the value is demonstrated, and if there are any unfortunate quirks due to how the macro is defined, they can be discovered in practice before it gets locked into std forever.

7 Likes

Maybe just do like this

fn foo(a: i32, b: i32, c: i32)->Option<i32> {
    dbg!(a,b,c);
    todo!()
}

It seems that we can just let those variables do anything and actually we don't care about what they actually do , a recommended way is to use dbg!()

2 Likes

I usually do something like

let _ignore = (a, b, c);
10 Likes

Why do the warnings bother you? After all you do want to be reminded of unused variables. So just ignore the warnings while you still write the function and have a look at them once you think you are done with the function. If a warning persists you forgot to do something. You can look at the warnings like a todo list.

I usually set #![allow(dead_code)] to the whole project during the initial structure exploration phase. Yes, there're tons of unused code but I don't care for now. After that phase I remove that line and hunt all the todo!()s down.

7 Likes

I agree in general that stuff should be first tested in a crate. In this particular example, using a crate for such a small thing feels like overkill, and also, you might want to remove the crate before shipping it.

Using dbg!(...) requires that the value implements Debug. In this case, we could actually do:

fn foo(a: i32, b: i32, c: i32)->Option<i32> {
    todo!("{:?}{:?}{:?}", a, b, c)
}

Consider:

let a = ...;
let b = ...;
let c = ...;
let d = ...;
todo!()  //  Planning to use a and c here

In this case, I get warnings for a, b, c and d. But in fact, I am planning to use a and c, but I forgot about b and d.

I think the point being made is that while your code contains todo!, having warnings shouldn't surprise you - it's "expected" that your code is not warning-free until you've reached the first complete version.

I'm not sure I agree with this point of view, but it is at least reasonable to say that you should fix errors, then todo!s, then come back round to handle warnings.

2 Likes

I wish rust-analyzer had code action that add _ to all arguments if there are todo!() and args are not used, and remove _ when started using them. Should I file an issue? Is it suitable as LSP's functionality?

In my view, adding _ to the argument name itself is supposed to be making the claim "I will never want to use this argument" which is typically not the claim you want to main if the reason for not using it was the todo.

7 Likes

Once something is in the standard library, it's "ossified" - it is effectively impossible to make any improvement that breaks backwards compatibility, since we want people to be able to upgrade their compiler version freely - if I'm using this in Rust 1.80, I should be able to upgrade to Rust 1.201.0 without having to fix anything.

Therefore, Rust's general policy is to put things in crates first, and get through all the evolution while it's in an external crate. If a particular piece of code is both popular and stable (not needing changes), then it's a candidate to move to the standard library; but it first needs to spend time outside the standard library demonstrating that it's stable and popular before it's ready to move across.

In addition to the backwards compatibility thing, also note that the standard library is a download every Rust user has to have; an external crate from crates.io is something that's downloaded only by people who use it. If it's unpopular, then we're making everyone pay the price of downloading it for only a limited number of users; putting it in an external crate allows you to prove that this is a reasonable price to demand of people, since the external crate becomes popular.

It depends on your brain I guess, but it's not for everyone so easy to mentally ignore warnings and other superfluous noise in your compiler output when you're in problem-solving mode, writing the stuff that isn't marked as todo!(). They literally disturb your thought process and at least to me are really annoying. The todo!() should already tell the compiler that yes, handling those unused variables is on my todo list, quit bothering me thankyou.

This problem is exacerbated by the fact that Rust is so weird about toggling lints. Cargo doesn't have flags for lints, RUSTFLAGS is decidedly not a good API, adding temporary crate-level attributes to the code itself is neither, and even though Cargo.toml now has [lints], they can't be customized on a per-profile basis (because profiles are "supposed" to only control codegen – otherwise it would be great to have an "in progress" profile and a "now let's tidy this up" profile).

1 Like

Though not stabilized yet, this could resolve this situation. 2383-lint-reasons - The Rust RFC Book

If we use it like the following, it suppresses the warning, but when the implementation is complete and those arguments are used, it will start to emit a warning that states there is no expected problem.

#[expect(unused_variable)]
fn foo(a: &A, b: &B, c: &C) -> Result<R, Error> {
  todo!()
}
2 Likes

There's an old closed PR with related discussion at Allow unused variables with todo! by charles-r-earp · Pull Request #79850 · rust-lang/rust · GitHub. I think the lint should just not fire at all if there's a todo! present after which the variable could be used, but at least at the time the lang team members weren't fans of the idea.

2 Likes

I've found this to be a pretty clean and easy way to silence the unused warnings:

fn foo(a: &A, b: &B, c: &C) -> Result<R, Error> {
  let _ = a;
  let _ = b;
  let _ = c;
  todo!()
}

or

fn foo(a: &A, b: &B, c: &C) -> Result<R, Error> {
  let _ = (a, b, c);
  todo!()
}
1 Like