coding-horror/basic-computer-games · error

Failed to read line.

Error message

Failed to read line.

What it means

The read_guess method on the WordGame struct calls read_line().expect("Failed to read line.") to read the player's letter guess. The panic fires on io::Error from read_line. On EOF (Ok(0)) the trimmed guess is empty; the validation loop iterates zero characters, so no invalid_input closure fires — the method likely returns false (no valid guess submitted), and the caller skips processing the guess, potentially looping forever depending on the calling game loop structure.

Source

Thrown at 96_Word/rust/src/word_game.rs:52

        self.guesses += 1;

        println!("\n\nGuess a five letter word?");

        let mut game_over = false;

        if WordGame::<'_>::read_guess(self) {
            game_over = WordGame::<'_>::process_guess(self);
        }

        game_over
    }

    fn read_guess(&mut self) -> bool {
        let mut guess = String::new();

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

        let invalid_input = |message: &str| {
            println!("\n{} Guess again.", message);
            return false;
        };

        let guess = guess.trim();

        for c in guess.chars() {
            if c.is_numeric() {
                return invalid_input("Your guess cannot include numbers.");
            }
            if !c.is_ascii_alphabetic() {
                return invalid_input("Your guess must only include ASCII characters.");
            }
        }

        if guess.len() != 5 {

View on GitHub (pinned to 5301155192)

Solutions

  1. Match on read_line; on Ok(0) return false and signal the caller to end the game.
  2. On Err(e), eprintln the error and return false so the caller can detect the failure.
  3. Add an explicit empty-input check after trimming: if guess.is_empty(), return false or break the game loop.
  4. For testing, ensure piped input covers every guess the game will request.

Example fix

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

// after
match io::stdin().read_line(&mut guess) {
    Ok(0) => return false, // EOF: no guess
    Ok(_) => {}
    Err(e) => {
        eprintln!("Input error: {e}");
        return false;
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Return false on EOF/error so the caller skips guess processing
fn read_guess(&mut self) -> bool {
    let mut guess = String::new();
    match io::stdin().read_line(&mut guess) {
        Ok(0) => return false,  // EOF: no guess
        Err(e) => {
            eprintln!("Input error: {e}");
            return false;
        }
        Ok(_) => { /* proceed with validation */ }
    }
    // ... rest of validation ...
}

Try / catch

// In read_guess method — match replaces expect
match io::stdin().read_line(&mut guess) {
    Ok(n) if n > 0 => { /* have input, validate characters */ }
    Ok(0) => return false,   // EOF: signal no valid guess
    Err(e) => {
        eprintln!("{e}");
        return false;
    }
}

Prevention

When it happens

Trigger: stdin pipe closed during gameplay. A terminal connection lost mid-guess. Non-interactive execution where stdin exhausts before the game ends.

Common situations: Piped input for automated Word game testing that runs out of guesses mid-game. Docker or CI without interactive stdin. Script that provides partial input.

Related errors


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