Handling Input Errors
Input operations in Rust can fail at two distinct layers:
- The I/O transport layer (
std::io::Error): OS read failures, broken pipes, premature End-of-File (UnexpectedEof), or non-UTF-8 byte sequences (InvalidData). - The domain parsing layer (
ParseIntError,ParseFloatError, custom validation): The UTF-8 text was read from the stream, but its contents do not match the expected grammar or numeric range.
Key std::io::ErrorKind Variants Encountered on Input
io::ErrorKind Variant |
When It Occurs on Input | Recommended Handling |
|---|---|---|
InvalidData |
read_line, lines, or read_to_string encountered bytes that are not valid UTF-8 |
Switch to byte-oriented .read_until(b'\n', &mut vec) + String::from_utf8_lossy if arbitrary bytes are expected |
UnexpectedEof |
read_exact(&mut buf) reached EOF before filling buf completely |
Report truncated file/frame or treat 0 bytes read on frame boundary as clean shutdown |
Interrupted (EINTR) |
A POSIX signal interrupted a blocking syscall before bytes were read | Automatically retried by read_exact, read_line, read_to_end; retry manually in raw .read() loops |
WouldBlock (EAGAIN) |
Nonblocking stream has no data ready right now | Register wakeup or poll again later |
PermissionDenied / NotFound |
Opening or reading a restricted or missing file | Surface path and OS error message to user |
Handling Invalid UTF-8 Input Without Failing (read_until + from_utf8_lossy)
Because BufRead::read_line(&mut String) and BufRead::lines() require valid UTF-8, a single non-UTF-8 byte (0xFF) in a log file or terminal stream causes read_line to return Err(io::ErrorKind::InvalidData).
When reading log files or streams that might contain arbitrary binary or Latin-1 bytes alongside ASCII text, read raw bytes into a Vec<u8> using reader.read_until(b'\n', &mut byte_buf) and convert with String::from_utf8_lossy(&byte_buf) (which borrows valid UTF-8 as Cow::Borrowed(&str) at zero cost and replaces invalid bytes with ``):
use std::io::{self, BufRead, Cursor};
fn main() -> io::Result<()> {
// Stream containing an invalid UTF-8 byte `0xFF` on line 1:
let non_utf8_stream: &[u8] = b"valid prefix \xFF invalid byte\nsecond clean line\n";
// 1. `read_line` into a `String` rejects invalid UTF-8 with `ErrorKind::InvalidData`:
let mut str_buf = String::new();
let utf8_err = Cursor::new(non_utf8_stream)
.read_line(&mut str_buf)
.unwrap_err();
println!("read_line error kind on 0xFF: {:?}", utf8_err.kind());
// 2. `read_until(b'\n', &mut byte_buf)` + `from_utf8_lossy` handles non-UTF-8 gracefully:
let mut reader = Cursor::new(non_utf8_stream);
let mut byte_buf = Vec::new();
while reader.read_until(b'\n', &mut byte_buf)? > 0 {
let lossy = String::from_utf8_lossy(&byte_buf);
println!("lossy line: {:?}", lossy.trim_end());
byte_buf.clear();
}
Ok(())
}
read_line error kind on 0xFF: InvalidData
lossy line: "valid prefix \u{fffd} invalid byte"
lossy line: "second clean line"
Validated Retry Loop for Interactive User Input
When prompting a user for typed input (such as an integer within a range 1..=100), separate stream EOF / I/O errors (which should exit the loop) from parse / validation errors (which should print a helpful message and prompt again):
use std::io::{self, BufRead, Cursor};
fn read_validated_port(mut reader: impl BufRead) -> io::Result<Option<u16>> {
let mut buf = String::new();
loop {
buf.clear();
if reader.read_line(&mut buf)? == 0 {
// Clean EOF reached without a valid port:
return Ok(None);
}
let trimmed = buf.trim();
match trimmed.parse::<u16>() {
Ok(port) if port >= 1024 => return Ok(Some(port)),
Ok(port) => println!("port {port} is privileged (< 1024); try again"),
Err(err) => println!("invalid integer {:?} ({err}); try again", trimmed),
}
}
}
fn main() -> io::Result<()> {
// User types "abc", then "80" (privileged), then "8080" (valid):
let simulated_user = Cursor::new("abc\n80\n8080\n");
let chosen_port = read_validated_port(simulated_user)?;
println!("accepted port: {:?}", chosen_port);
Ok(())
}
invalid integer "abc" (invalid digit found in string); try again
port 80 is privileged (< 1024); try again
accepted port: Some(8080)