coding-horror/basic-computer-games · error

Failed to read line.

Error message

Failed to read line.

What it means

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.

Source

Thrown at 62_Mugwump/rust/src/util.rs:10

use std::io;

pub fn prompt(msg: &str) -> String {
    println!("\n{}", msg);

    let mut input = String::new();

    io::stdin()
        .read_line(&mut input)
        .expect("Failed to read line.");

    input.trim().to_string()
}

pub fn prompt_bool(msg: &str) -> Option<bool> {
    loop {
        let response = prompt(msg);

        match response.to_uppercase().as_str() {
            "Y" | "YES" => return Some(true),
            "N" | "NO" => return Some(false),
            _ => println!("PLEASE ENTER (Y)ES or (N)O."),
        }
    }
}

View on GitHub (pinned to 5301155192)

Solutions

  1. Change `prompt()` to return `Option<String>` or `Result<String, io::Error>`, returning `None`/`Err` on read failure instead of panicking.
  2. Detect EOF (0 bytes) and signal the caller to exit the game cleanly.
  3. When running non-interactively, provide an input stream that contains a line for every prompt the game will issue.

Example fix

// before
pub fn prompt(msg: &str) -> String {
    println!("\n{}", msg);
    let mut input = String::new();
    io::stdin().read_line(&mut input).expect("Failed to read line.");
    input.trim().to_string()
}

// after
pub fn prompt(msg: &str) -> Option<String> {
    println!("\n{}", msg);
    let mut input = String::new();
    match io::stdin().read_line(&mut input) {
        Ok(0) => None,
        Ok(_) => Some(input.trim().to_string()),
        Err(_) => None,
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate that stdin is readable before calling prompt()
use std::io::IsTerminal;
if !io::stdin().is_terminal() && std::env::var("INTERACTIVE_TEST").is_err() {
    eprintln!("Warning: no interactive stdin detected.");
}

Type guard

pub fn prompt(msg: &str) -> Option<String> {
    println!("\n{}", msg);
    let mut input = String::new();
    match io::stdin().read_line(&mut input) {
        Ok(0) | Err(_) => None,
        Ok(_) => Some(input.trim().to_string()),
    }
}

Try / catch

match io::stdin().read_line(&mut input) {
    Ok(0) => { println!("\nGoodbye!"); std::process::exit(0); }
    Ok(_) => { /* proceed */ }
    Err(_) => { println!("Input error. Try again."); }
}

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Related errors


AI-assisted analysis of coding-horror/basic-computer-games@5301155192 (2026-08-13). Data as JSON: /api/errors/f4194174223e9922. Report an issue: GitHub.