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

  1. 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).
  2. Distinguish EOF (graceful exit, return false) from I/O error (log and exit) so the silent 'no' behavior becomes an explicit decision.
  3. For automated testing, ensure every yes/no prompt in the game flow receives a line.
  4. 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

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.