Skip to content

never_loop fires from break on the RHS of lazy boolean operator #16462

@steffahn

Description

@steffahn

Summary

In this discussion I came up with a silly way of doing a “do { … } while (…);”-style loop in Rust; trying it in the playground I noticed that clippy::never_loop fires even though the code (while a bit silly / unconventional) does in fact work [and the loop is not actually never looping].

Lint Name

clippy::never_loop

Reproducer

I tried this code:

fn main() {
    let mut n = 10;
    loop {
        println!("{n}");
        n -= 1;
        n >= 0 || break;
    }
}

(playground)

The code compiles, and runs, and looping 11 times

warning: unused logical operation that must be used
 --> src/main.rs:6:8
  |
6 |         n >= 0 || break;
  |         ^^^^^^^^^^^^^^^ the logical operation produces a value
  |
  = note: `#[warn(unused_must_use)]` (part of `#[warn(unused)]`) on by default
help: use `let _ = ...` to ignore the resulting value
  |
6 |         let _ = n >= 0 || break;
  |         +++++++
10
9
8
7
6
5
4
3
2
1
0

But clippy complains, it’s even considered an error by default :-)

error: this loop never actually loops
 --> src/main.rs:3:5
  |
3 | /     loop {
4 | |         println!("{n}");
5 | |         n -= 1;
6 | |         n >= 0 || break;
7 | |     }
  | |_____^
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#never_loop
  = note: `#[deny(clippy::never_loop)]` on by default

(additional clippy warnings-level lints, [not error-level])

warning: boolean short circuit operator in statement may be clearer using an explicit test
 --> src/main.rs:6:8
  |
6 |         n >= 0 || break;
  |         ^^^^^^^^^^^^^^^^ help: replace it with: `if n < 0 { break; }`
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#short_circuit_statement
  = note: `#[warn(clippy::short_circuit_statement)]` on by default

warning: sub-expression diverges
 --> src/main.rs:6:18
  |
6 |         n >= 0 || break;
  |                   ^^^^^
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#diverging_sub_expression
  = note: `#[warn(clippy::diverging_sub_expression)]` on by default

I expected to see this happen:

clippy::never_loop should not fire here at all, since its claim “this loop never actually loops” is plainly incorrect in this case.

Version

Rust 1.93.0 in the playground, also 1.95.0-nightly (2026-01-24 f134bbc78dac04a17324) in the playground does the same.

Metadata

Metadata

Assignees

Labels

C-bugCategory: Clippy is not doing the correct thingI-false-positiveIssue: The lint was triggered on code it shouldn't have

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions