{"record":{"id":"379277bbb16b78f2","repo":"coding-horror/basic-computer-games","slug":"failed-to-read-line-379277","errorCode":null,"errorMessage":"Failed to read line.","messagePattern":"Failed to read line\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"90_Tower/rust/src/util.rs","lineNumber":17,"sourceCode":"use std::io;\n\npub enum PromptResult {\n    Number(i32),\n    YesNo(bool),\n}\n\npub fn prompt(numeric: bool, msg: &str) -> PromptResult {\n    use PromptResult::*;\n    loop {\n        println!(\"{}\", msg);\n\n        let mut input = String::new();\n\n        io::stdin()\n            .read_line(&mut input)\n            .expect(\"Failed to read line.\");\n\n        let input = input.trim().to_string();\n\n        if numeric {\n            if let Ok(n) = input.parse::<i32>() {\n                return Number(n);\n            }\n\n            println!(\"PLEASE ENTER A NUMBER.\")\n        } else {\n            match input.to_uppercase().as_str() {\n                \"YES\" | \"Y\" => return YesNo(true),\n                \"NO\" | \"N\" => return YesNo(false),\n                _ => println!(\"PLEASE ENTER (Y)ES OR (N)O.\"),\n            }\n        }\n    }\n}","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/coding-horror/basic-computer-games/blob/5301155192d91d74d337899cecc59dbda59c4c17/90_Tower/rust/src/util.rs#L1-L35","documentation":"This is the central input utility for Tower of Hanoi: a reusable prompt() function that loops until valid input is received, then returns a PromptResult enum (Number or YesNo). The .expect(\"Failed to read line.\") panics on io::Error. Critically, on EOF (Ok(0)) the function does NOT panic — empty input never matches a valid i32 parse or yes/no arm, so it prints the re-prompt and loops. Because EOF is sticky on stdin, this creates an infinite busy-loop that floods stdout with the prompt message forever. No caller catches the panic because PromptResult has no error variant.","triggerScenarios":"Running Tower of Hanoi non-interactively with stdin from /dev/null or an exhausted pipe — causes the infinite-loop hang rather than the panic. A broken pipe or terminal driver error — causes the panic. Any of the four call sites (get_disk_count, get_disk_to_move, ask_which_needle, play-again loop) hitting an I/O failure.","commonSituations":"CI test that pipes disk counts and move sequences without enough lines. Docker container without -it. Automated UI testing that closes stdin early. Script that feeds partial input and then closes.","solutions":["Match on read_line; on Ok(0) (EOF) call std::process::exit(0) or return a sentinel to end the infinite loop — this is the most impactful fix since the hang is worse than the panic.","Add a None or Eof variant to PromptResult so callers can react to input exhaustion instead of hanging or crashing.","On Err(e), eprintln the error and process::exit(1) for a clean shutdown with a diagnostic.","For testing, ensure piped input has a line for every prompt the game will issue across all turns."],"exampleFix":"// before\nio::stdin()\n    .read_line(&mut input)\n    .expect(\"Failed to read line.\");\n\n// after\nmatch io::stdin().read_line(&mut input) {\n    Ok(0) => std::process::exit(0),\n    Ok(_) => {}\n    Err(e) => {\n        eprintln!(\"Input error: {e}\");\n        std::process::exit(1);\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Critical: handle BOTH Err (panic) and Ok(0) (infinite loop)\nmatch io::stdin().read_line(&mut input) {\n    Ok(0) => std::process::exit(0),  // EOF: stop the infinite re-prompt\n    Ok(_) => { /* safe to trim and parse */ }\n    Err(e) => {\n        eprintln!(\"Input error: {e}\");\n        std::process::exit(1);\n    }\n}","typeGuard":"// If refactoring PromptResult to carry an EOF variant:\n// pub enum PromptResult {\n//     Number(i32),\n//     YesNo(bool),\n//     Eof,  // new variant for input exhaustion\n// }\n// Then callers can match on Eof to exit cleanly.","tryCatchPattern":"// In the reusable prompt() function — must handle both failure modes\nloop {\n    println!(\"{}\", msg);\n    let mut input = String::new();\n    match io::stdin().read_line(&mut input) {\n        Ok(n) if n > 0 => { /* proceed with validation */ }\n        Ok(0) => std::process::exit(0),  // EOF: prevent infinite loop\n        Err(e) => {\n            eprintln!(\"Input error: {e}\");\n            std::process::exit(1);       // I/O error: prevent panic\n        }\n    }\n    // ... rest of parsing logic ...\n}","preventionTips":["For reusable input utilities, always handle Ok(0) — infinite loops are worse than panics for users.","Add an Eof variant to result enums so callers can react to input exhaustion.","Test utility functions with /dev/null stdin to verify they exit, not hang.","Consider returning Result<PromptResult, io::Error> from utility functions so callers control error policy.","Log before process::exit so CI logs show why the program stopped."],"tags":["rust","io","stdin","panic","infinite-loop","utility","sticky-eof","tower-of-hanoi"],"backgroundTag":null,"analyzedSha":"5301155192d91d74d337899cecc59dbda59c4c17","analyzedAt":"2026-08-13T19:29:00.979Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}