coding-horror/basic-computer-games · error
CANNOT READ INPUT!
Error message
CANNOT READ INPUT!
What it means
The get_yes_no_from_user_input helper for the War card game calls read_line().expect("CANNOT READ INPUT!") to read a single yes/no answer. The panic fires on io::Error only. On EOF (Ok(0)) the buffer is empty, the trimmed string has no first char, and the function falls through to return false — meaning the game silently treats disconnected stdin as 'no' and exits without error. This is a silent-data-loss behavior, not a crash, but the expect panic itself is what is logged.
Source
Thrown at 94_War/rust/src/main.rs:148
The computer gives you and it a 'card'. The higher card
(numerically) wins. The game ends when you choose not to
continue or when you have finished the pack.\n
");
}
}
/**
* returns true if user input starts with y or Y,
* false otherwise
*/
fn get_yes_no_from_user_input(prompt: &str) -> bool {
let mut raw_input = String::new(); // temporary variable for user input that can be parsed later
//print prompt
println!("{}", prompt);
//read user input from standard input, and store it to raw_input
//raw_input.clear(); //clear input
io::stdin().read_line(&mut raw_input).expect( "CANNOT READ INPUT!");
//from input, try to read a valid character
if let Some(i) = raw_input.trim().chars().nth(0) {
if i == 'y' || i == 'Y' {
return true;
}
}
//default case
return false;
}
View on GitHub (pinned to 5301155192)
Solutions
- Replace .expect() with a match; on Ok(0) log 'input closed' and return false; on Err(e) eprintln and return false or process::exit(1).
- Distinguish EOF (graceful exit, return false) from I/O error (log and exit) so the silent 'no' behavior becomes an explicit decision.
- For automated testing, ensure every yes/no prompt in the game flow receives a line.
- Wrap read_line in a helper that returns Option<String> (None on EOF/error) to centralize the logic for all three call sites.
Example fix
// before
io::stdin().read_line(&mut raw_input).expect("CANNOT READ INPUT!");
// after
match io::stdin().read_line(&mut raw_input) {
Ok(0) => return false, // EOF: treat as "no"
Ok(_) => {}
Err(e) => {
eprintln!("Input error: {e}");
return false;
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// Distinguish EOF (graceful false) from error (logged false)
fn get_yes_no_from_user_input(prompt: &str) -> bool {
let mut raw_input = String::new();
println!("{}", prompt);
match io::stdin().read_line(&mut raw_input) {
Ok(0) => return false, // EOF: treat as "no"
Err(e) => {
eprintln!("Input error: {e}");
return false;
}
Ok(_) => {}
}
raw_input.trim().starts_with(['y', 'Y'])
} Try / catch
// Centralized yes/no reader — replace the expect-based version
match io::stdin().read_line(&mut raw_input) {
Ok(n) if n > 0 => {
raw_input.trim().chars().next()
.map(|c| c == 'y' || c == 'Y')
.unwrap_or(false)
}
_ => false, // EOF or error: default to "no"
} Prevention
- Centralize stdin reading in one helper so EOF/error handling is consistent across all three call sites.
- Use .starts_with(['y','Y']) instead of chars().nth(0) for clarity and zero-allocation.
- Test the game with piped input that closes mid-session to verify graceful exit.
- Distinguish 'user chose no' from 'input unavailable' in logs to aid debugging.
When it happens
Trigger: A broken stdin pipe during any of the three prompt points (continue, play again, directions). A terminal device error. Running under a supervisor that revokes stdin.
Common situations: Piped input from a file that closes between turns. Non-interactive execution where stdin is /dev/null (Ok(0) path, silent 'no'). CI or Docker without -it.
Related errors
AI-assisted analysis of coding-horror/basic-computer-games@5301155192 (2026-08-13).
Data as JSON: /api/errors/7e79bdee5d3fad01.
Report an issue: GitHub.