Skip to content

unimplemented!() and todo!() both expand to a runtime panic. They're fine as scaffolding while you sketch a function or stub a trait impl during development — but shipping them in library code means any consumer who walks that path crashes with no useful diagnostic.

The check fires on unimplemented!() and todo!() invocations that are not inside a test context. A call counts as in test context when any of its ancestors is:

  • a function or module annotated with #[cfg(test)],
  • a function annotated with #[test],
  • or a module named tests.

Example

rust
// FIRES: lib code, no test guard.
pub fn next_step() -> i32 {
    todo!()  // -> Rust.NoUnimplementedInNonTest
}

// DOES NOT FIRE: enclosing function is #[test].
#[test]
fn placeholder_test() {
    todo!("fill in once the API stabilises");
}

// DOES NOT FIRE: enclosing module is #[cfg(test)].
#[cfg(test)]
mod tests {
    fn fixture_helper() -> i32 {
        unimplemented!()
    }
}

What to do

  • Implement the function. If the implementation is genuinely deferred, open an issue and link to it from the surrounding context — but don't ship a panic.
  • If the function is a trait method that doesn't apply, return a default value, return an error, or restructure the trait so the method isn't required for this implementer.
  • If the call is during in-progress scaffolding, move it under a #[cfg(test)] gate or behind a feature flag while you work.

Suppression

Inline directives narrow the suppression to the specific call:

rust
// cofferdam-ignore: Rust.NoUnimplementedInNonTest: temporary — tracked in <issue>
pub fn next_step() -> i32 {
    todo!()
}

Suppressions on unimplemented!() / todo!() should always link to the tracking issue. They are by definition known incompleteness — if no issue exists, the suppression masks technical debt.

MIT License