{"record":{"id":"f4194174223e9922","repo":"coding-horror/basic-computer-games","slug":"failed-to-read-line-f41941","errorCode":null,"errorMessage":"Failed to read line.","messagePattern":"Failed to read line\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"62_Mugwump/rust/src/util.rs","lineNumber":10,"sourceCode":"use std::io;\n\npub fn prompt(msg: &str) -> String {\n    println!(\"\\n{}\", 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    input.trim().to_string()\n}\n\npub fn prompt_bool(msg: &str) -> Option<bool> {\n    loop {\n        let response = prompt(msg);\n\n        match response.to_uppercase().as_str() {\n            \"Y\" | \"YES\" => return Some(true),\n            \"N\" | \"NO\" => return Some(false),\n            _ => println!(\"PLEASE ENTER (Y)ES or (N)O.\"),\n        }\n    }\n}\n","sourceCodeStart":1,"sourceCodeEnd":26,"githubUrl":"https://github.com/coding-horror/basic-computer-games/blob/5301155192d91d74d337899cecc59dbda59c4c17/62_Mugwump/rust/src/util.rs#L1-L26","documentation":"This panic is triggered by `.expect(\"Failed to read line.\")` on `io::stdin().read_line()` inside the shared `prompt()` utility in Mugwump's `util.rs` (line 10). Because this is a reusable prompt function called by all game input sites, a stdin failure here crashes the program at *any* prompt, not just one specific interaction.","triggerScenarios":"`read_line` returns `Err` or EOF. Since `prompt()` returns `String` (not `Result`), there is no way for callers to handle a read failure — the `.expect` is the only failure path. The function is used for all text input in the game.","commonSituations":"Non-interactive execution, piped input that runs out, terminal disconnection. Because every input goes through this function, any of dozens of game prompts can trigger it.","solutions":["Change `prompt()` to return `Option<String>` or `Result<String, io::Error>`, returning `None`/`Err` on read failure instead of panicking.","Detect EOF (0 bytes) and signal the caller to exit the game cleanly.","When running non-interactively, provide an input stream that contains a line for every prompt the game will issue."],"exampleFix":"// before\npub fn prompt(msg: &str) -> String {\n    println!(\"\\n{}\", msg);\n    let mut input = String::new();\n    io::stdin().read_line(&mut input).expect(\"Failed to read line.\");\n    input.trim().to_string()\n}\n\n// after\npub fn prompt(msg: &str) -> Option<String> {\n    println!(\"\\n{}\", msg);\n    let mut input = String::new();\n    match io::stdin().read_line(&mut input) {\n        Ok(0) => None,\n        Ok(_) => Some(input.trim().to_string()),\n        Err(_) => None,\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Validate that stdin is readable before calling prompt()\nuse std::io::IsTerminal;\nif !io::stdin().is_terminal() && std::env::var(\"INTERACTIVE_TEST\").is_err() {\n    eprintln!(\"Warning: no interactive stdin detected.\");\n}","typeGuard":"pub fn prompt(msg: &str) -> Option<String> {\n    println!(\"\\n{}\", msg);\n    let mut input = String::new();\n    match io::stdin().read_line(&mut input) {\n        Ok(0) | Err(_) => None,\n        Ok(_) => Some(input.trim().to_string()),\n    }\n}","tryCatchPattern":"match io::stdin().read_line(&mut input) {\n    Ok(0) => { println!(\"\\nGoodbye!\"); std::process::exit(0); }\n    Ok(_) => { /* proceed */ }\n    Err(_) => { println!(\"Input error. Try again.\"); }\n}","preventionTips":["Design shared input utilities to return Option/Result, not String, so callers can handle failures.","Centralize stdin error handling in one safe function rather than per-call .expect().","Test all interactive paths with both piped and TTY stdin."],"tags":["rust","stdin","io","panic","expect","utility-function","cli-game"],"backgroundTag":null,"analyzedSha":"5301155192d91d74d337899cecc59dbda59c4c17","analyzedAt":"2026-08-13T19:29:00.979Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}