rust-lang / rust-lang/rust-clippy

`never_loop` false-positive when using join_handles

Open
#16,054 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

C-bug I-false-positive
Dominant language
Rust
Stars
13.5k
Forks
2.2k
Avg merge
2d 10h
Merged PRs (30d)
32

Description

Summary

When using a JoinHandle, Clippy seems to think that loops will never pass.

Lint Name

never_loop

Reproducer

I tried this code:

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

#[derive(Clone)]
struct Philosopher {
    pub fork1: Arc<Mutex<usize>>,
    pub fork2: Arc<Mutex<usize>>,
    pub is_eating: bool,
    pub id: usize,
}

impl Philosopher {
    pub fn eat(&mut self) {
        {
            if let Ok(_) = self.fork1.lock() && let Ok(_) = self.fork2.lock() {
                self.is_eating = true;
            }
            println!("Philosopher {} is eating!", self.id);
            thread::sleep(Duration::from_secs(1));
        }
        self.is_eating = false;
    }
}

fn algo() {
    let forks: [Arc<Mutex<usize>>; 5] = core::array::from_fn(|x| Arc::new(Mutex::new(x)));
    let philosophers: [Arc<Mutex<Philosopher>>; 5] = core::array::from_fn(|x| Arc::new(Mutex::new(Philosopher {fork1: Arc::clone(&forks[x % 5]), fork2: Arc::clone(&forks[(x + 1) % 5]), is_eating: false, id: x})));
    let mut join_handles = Vec::new();
    for i in 0..5 {
        let value = philosophers.clone();
        let join_handle = thread::spawn(move || {
            let binding = Arc::clone(&value[i]);
            let mut philosopher = binding.lock().unwrap();
            loop {
                philosopher.eat();
            }
        });
        join_handles.push(join_handle);
    }
    for join_handle in join_handles {
        join_handle.join().unwrap();
    }
}

fn main() {
    algo();
}

I saw this happen:

error: this loop never actually loops                                                                                                                                           
  --> src\main.rs:41:5
   |
41 | /     for join_handle in join_handles {
42 | |         join_handle.join().unwrap();
43 | |     }
   | |_____^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#never_loop
   = note: `#[deny(clippy::never_loop)]` on by default
help: if you need the first element of the iterator, try writing
   |
41 -     for join_handle in join_handles {
41 +     if let Some(join_handle) = join_handles.into_iter().next() {
   |

I expected to see this happen:
The code passes without error

Version
rustc 1.91.0 (f8297e351 2025-10-28)
binary: rustc
commit-hash: f8297e351a40c1439a467bbbb6879088047f50b3
commit-date: 2025-10-28
host: x86_64-pc-windows-msvc
release: 1.91.0
LLVM version: 21.1.2
Additional Labels

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the report from src/main.rs and trace the never_loop lint's handling of the join_handles loop, comparing it with the inner loop. Done means the provided example no longer emits this false positive and the behavior is covered by a regression test.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.