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) returning Ok(0) means the remote peer performed an orderly shutdown (close or Shutdown::Write), sending a TCP FIN packet after all in-flight bytes were delivered.
  • stream.read(&mut buf) returning Err(ErrorKind::ConnectionReset) means the remote peer crashed or aborted the connection via a TCP RST packet, indicating that in-flight data may have been lost.