This has come up multiple times, but there isn't a team consensus in favor of it. It's a matter of preference: some people prefer that todo!() suppress warnings, others want to continue to see those warnings until they're fixed. In the absence of a consensus to change, we keep the current behavior.
While todo! can be compiler magic, I question how a macro would apply an attribute to the enclosing scope. By what mechanism could this be described? Would it apply to the enclosing fn no matter how nested? What about within a closure within a function?
I have a related nit when implementing the default behavior for a trait method that doesn't use all parameters: I want to keep the name as used in common cases instead of prefixing with _ and also avoid using "ignore all unused variables" instead of "just this one". C and C++ have (void)unused; to indicate it; I don't think Rust has such a spelling beyond #[used] on the declaration (which doesn't work very narrowly for conditionally used variables).
It's not about the "right" behavior, but about providing a configurable mechanism so people can choose. The default can by all means stay as is.
My question is how can Rust be extended so that those who want a more sophisticated treatment of todo!() can configure their workflow accordingly, and also how could I help to implement this.
I don't want to repeat the previous discussions but the major points which seemed to stand out where:
Disabling all "unused_variables" does not work, because it is an important warning.
Putting an additional hint for those functions (like #![allow(unused_variables)]) adds considerable overhead and is dangerous as one might forget to remove it.
todo!() has different semantics than unimplemented!().
I am unfortunately not familiar with the Rust compiler, but couldn't something like this work: Analyze a function and find out if any execution paths ends in "todo!()". If so first produce a (optional) warning a la "warning: you have still work to do!" and then (optionally) suppress other "unused_variables" warnings triggered by this function.
This feels like it makes sense, but doesn't really solve the normal loop of "write a function with todo, get a warning, go back and suppress the warning". todo! is supposed to make it quick and easy.
I think most helpful/general would be a way to suppress warnings within a function from within a function, so the todo! macro could just include that suppression. But that still might be more effort than it's worth, if this is its only use. And it still doesn't quite cover todos that aren't top level in a function, but it still might be enough.
Ultimately my experience has been that in the transient state of a module where I'd have todo!s, I generally want a blanket #[allow(unused)] on the whole module anyway, because I'll have partial function/type drafts present which aren't "used" yet.
The papercut that annoys me more is todo! triggering unreachable_code on (and thus also sometimes hiding some borrowck errors in) anything following; I want to be able to use todo! as a hole (potentially multiple) in the interior of a function while I sketch it out top-down control flow (without needing to write out the holes' function signatures until necessary for type inference[1]).
My opinion: either todo! should suppress unused_variables or it shouldn't cause unreachable_code. Having unused_variables is nice when using todo! as a typed impl hole, but unreachable_code ends up discouraging such non-tail usage.
One reasonably straightforward case (although quite adhoc and probably far from simple to detect during lint passes) would be a function body of only a todo!. That case shouldn't lint for unused function parameters for the same reason trait method declarations without default bodies don't — in this state it's obviously just a declaration for an impl to be applied to later.
(But when param bounds permit, I do like to use todo!("fn_name({arg_name:?})") for quicker identification, which does actually use the parameters.
Maybe I should familiarize myself with the "generate function" intent in my IDE more... ↩︎
I do this too (and ignore unused when refactoring and expanding API space as well). I hadn't thought of the annoying aspects from the perspective of function boundaries:
// This issues no warnings
pub fn f(i: i32) {
let j: i32 = (|| todo!())();
println!("{}", i + j);
}
I was pretty ambivalent around the original proposal,[1] but this behavior is nicer for non-tail todo!(). Perhaps there should be a single warning per "lazy todo" or a separate warning category.[2]
FWIW, clippy has clippy::todo for linting uses of todo! (although that doesn't catch // TODO comments, ofc).
That is interesting how type inference works here (I suppose due to !'s pseudotype status and closures' inference hacks). If you enable #![feature(never_type)]and annotate the closure as returning !, then you get an unreachable_code warning again. If you don't, then the type inference favors giving the closure the return type determined by usage, pushing the ! as i32 coercion into the body of the closure, thus "hiding" the fact that anything after the closure is invoked is unreachable.
If you're sticking it in a macro anyway, might as well stick #[track_caller] on the closure to strip the otherwise meaningless closure stack frame. Your value_of! is basically a declval except guarded by a runtime panic instead of a codegen error.
I generally grep for todos (that may also be in comments, say) as part of reaching a dev milestone, but warnings are more reliable. ↩︎
Mh, I think this is a matter of preference but also a matter of use cases.
If you are a library implementor the todo! is good for emitting warnings, if you are a library user todo! may avoid emitting warnings because maybe the feature that the library is forcing you to implement (with a trait) it not a priority for you.
I am most of the time myself inside the library implementor side, but I think we can allow the user to define a top-level feature to tell (Please do not emit winning when I was the todo macro), this should make everyone happy.
This is one of those situations where I distinguish between todo!() ("I am going to implement this, but just haven't got around to it yet") and unimplemented!() ("I might never implement this, because it's not required").
Another annoying feature of todo!() is that you can't use it in functions returning impl Trait, i.e. this code is an error
fn foo() -> impl Bar { todo!() }
because inference treats a panic as (), and it doesn't implement Bar in general.
I get why in general panics are treated that way, but for todo!() specifically it would be very useful if the type checker would just shut up and acted as if everything typechecks. That's the way assume, believeme etc directives work in dependently-typed languages, for example. Whatever obligations were outstanding, they are discharged.
I agree that todo!() should not trigger the unreachable_code warning, but I don't think it should silence unused_variables by default. It would be better to allow it to explicitly accept values to mark them as "used", i.e. we could write todo!(foo, bar);. It would have no effect on the panic message, but for programmers it would be an indication to "add code which uses foo and bar".
UPD: We already can write todo!("{foo} {bar}"), but it requires implementation of a formatting trait by the types.
It would be nice if todo!() returned ! here and somehow we guaranteed that ! implemented Bar and other traits automatically (with all associated types also set to ! and all methods panicking)