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 Splat's `read_line()` utility function in `utility.rs` (line 22). This function trims and uppercases the input before returning. Because it returns `String` (not `Result`), there is no caller-side error recovery — the `.expect` is the sole failure path. The function is used by `prompt_bool` and other game input routines throughout Splat.

Source

Thrown at 81_Splat/rust/src/utility.rs:22

const DEATH_MESSAGES: [&str; 10] = [
    "REQUIESCAT IN PACE.",
    "MAY THE ANGEL OF HEAVEN LEAD YOU INTO PARADISE.",
    "REST IN PEACE.",
    "SON-OF-A-GUN.",
    "#$%&&%!$",
    "A KICK IN THE PANTS IS A BOOST IF YOU'RE HEADED RIGHT.",
    "HMMM. SHOULD HAVE PICKED A SHORTER TIME.",
    "MUTTER. MUTTER. MUTTER.",
    "PUSHING UP DAISIES.",
    "EASY COME, EASY GO.",
];

pub fn read_line() -> String {
    let mut input = String::new();
    io::stdin()
        .read_line(&mut input)
        .expect("Failed to read line.");
    input.trim().to_uppercase()
}

pub fn prompt_bool(msg: &str, template: bool) -> bool {
    if template {
        println!("{} (YES OR NO)?", msg);
    } else {
        println!("{}", msg);
    }

    loop {
        let response = read_line();

        match response.as_str() {
            "YES" => return true,
            "NO" => return false,
            _ => println!("PLEASE ENTER YES OR NO."),
        }

View on GitHub (pinned to 5301155192)

Solutions

  1. Change `read_line()` to return `Option<String>`, mapping EOF and errors to `None`.
  2. Callers should check `None` and exit the game gracefully or retry.
  3. Provide complete input fixtures for non-interactive runs.

Example fix

// before
pub fn read_line() -> String {
    let mut input = String::new();
    io::stdin().read_line(&mut input).expect("Failed to read line.");
    input.trim().to_uppercase()
}

// after
pub fn read_line() -> Option<String> {
    let mut input = String::new();
    match io::stdin().read_line(&mut input) {
        Ok(0) | Err(_) => None,
        Ok(_) => Some(input.trim().to_uppercase()),
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

use std::io::IsTerminal;
if !io::stdin().is_terminal() {
    eprintln!("Warning: non-interactive stdin.");
}

Type guard

pub fn read_line() -> Option<String> {
    let mut input = String::new();
    match io::stdin().read_line(&mut input) {
        Ok(0) | Err(_) => None,
        Ok(_) => Some(input.trim().to_uppercase()),
    }
}

Try / catch

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

Prevention

When it happens

Trigger: Stdin returns `Err` or EOF. The function is the primary input path for the entire game, so a failure here crashes Splat at any user prompt. The `to_uppercase()` conversion happens after `read_line` succeeds, so encoding issues do not trigger this panic.

Common situations: Non-interactive execution. Piped input that ends before the game session completes. Terminal disconnection. Running under CI or a service manager without a TTY.

Related errors


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