Networking with the Standard Library
Network Error Handling
Network operations are inherently fallible: cables unplug, remote servers restart, ports are already in use, and firewalls drop connections mid-stream. Rust surfaces all socket errors through std::io::Result<T> (Result<T, std::io::Error>) and classifies them via std::io::ErrorKind so recovery logic can distinguish transient glitches from fatal errors.
Common Network io::ErrorKind Variants
ErrorKind |
Typical OS Error (errno / WSA) |
Cause & Recommended Recovery Strategy |
|---|---|---|
AddrInUse |
EADDRINUSE |
Another process is already bound to the target IP and port. Choose another port or stop the conflicting process. |
AddrNotAvailable |
EADDRNOTAVAIL |
Attempted to bind to an IP address not assigned to any local interface. |
ConnectionRefused |
ECONNREFUSED |
Target host is reachable, but nothing is listening on the destination port. Retry with backoff if waiting for service startup. |
ConnectionReset |
ECONNRESET |
Remote peer forcefully closed the connection (sent a TCP RST). Close the socket and clean up session state. |
ConnectionAborted |
ECONNABORTED |
Connection was aborted locally by the host kernel or software. |
BrokenPipe |
EPIPE |
Attempted to write to a socket whose read half was already closed. |
TimedOut / WouldBlock |
ETIMEDOUT / EAGAIN |
Connect, read, or write timeout expired, or non-blocking socket isn't ready. |
Interrupted |
EINTR |
System call was interrupted by a Unix signal before any bytes were transferred. Retry the operation immediately. |
UnexpectedEof |
(Generated by read_exact) |
Peer closed the connection (read returned 0) before the expected number of bytes arrived. |
Classifying Transient vs Terminal Network Errors
use std::io::{self, ErrorKind};
use std::net::{TcpListener, TcpStream};
#[derive(Debug, PartialEq, Eq)]
enum FailureClass {
RetryImmediately,
RetryWithBackoff,
PeerDisconnected,
ConfigurationError,
}
fn classify_net_error(err: &io::Error) -> FailureClass {
match err.kind() {
ErrorKind::Interrupted => FailureClass::RetryImmediately,
ErrorKind::WouldBlock
| ErrorKind::TimedOut
| ErrorKind::ConnectionRefused => FailureClass::RetryWithBackoff,
ErrorKind::ConnectionReset
| ErrorKind::ConnectionAborted
| ErrorKind::BrokenPipe
| ErrorKind::UnexpectedEof => FailureClass::PeerDisconnected,
ErrorKind::AddrInUse
| ErrorKind::AddrNotAvailable
| ErrorKind::PermissionDenied
| ErrorKind::InvalidInput => FailureClass::ConfigurationError,
_ => FailureClass::PeerDisconnected,
}
}
fn main() -> io::Result<()> {
let listener1 = TcpListener::bind("127.0.0.1:0")?;
let occupied_addr = listener1.local_addr()?;
// 1. Trigger AddrInUse by binding a second listener to the exact same port
let dup_bind_err = TcpListener::bind(occupied_addr).unwrap_err();
println!(
"Duplicate bind -> kind: {:?}, class: {:?}",
dup_bind_err.kind(),
classify_net_error(&dup_bind_err)
);
// 2. Drop the listener so the port is closed, then trigger ConnectionRefused
drop(listener1);
if let Err(conn_err) = TcpStream::connect(occupied_addr) {
println!(
"Closed port connect -> kind: {:?}, class: {:?}",
conn_err.kind(),
classify_net_error(&conn_err)
);
}
Ok(())
}
Duplicate bind -> kind: AddrInUse, class: ConfigurationError
Closed port connect -> kind: ConnectionRefused, class: RetryWithBackoff
Distinguishing Clean EOF (Ok(0)) from ConnectionReset
When reading from a TcpStream:
stream.read(&mut buf)returningOk(0)means the remote peer performed an orderly shutdown (closeorShutdown::Write), sending a TCPFINpacket after all in-flight bytes were delivered.stream.read(&mut buf)returningErr(ErrorKind::ConnectionReset)means the remote peer crashed or aborted the connection via a TCPRSTpacket, indicating that in-flight data may have been lost.