Networking with the Standard Library

Socket Addresses

A socket address pairs an IP address with a 16-bit transport port number (u16) to identify a specific communication endpoint on a host. Rust represents socket addresses with the enum SocketAddr (SocketAddr::V4(SocketAddrV4) and SocketAddr::V6(SocketAddrV6)).

SocketAddr::V4(SocketAddrV4)
  +-- ip:   Ipv4Addr (e.g., 127.0.0.1)
  +-- port: u16      (e.g., 8080)
  Text format: "127.0.0.1:8080"

SocketAddr::V6(SocketAddrV6)
  +-- ip:       Ipv6Addr (e.g., ::1)
  +-- port:     u16      (e.g., 8080)
  +-- flowinfo: u32      (IPv6 traffic flow label, usually 0)
  +-- scope_id: u32      (Link-local interface zone index, usually 0)
  Text format: "[::1]:8080"

Constructing and Parsing Socket Addresses

Notice that when formatting or parsing an IPv6 socket address as a string, the IPv6 address must be enclosed in square brackets ([::1]:8080) to disambiguate the colons inside the IPv6 address from the :port separator.

use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};

fn main() {
    // 1. Direct construction
    let v4 = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8080);
    let v6 = SocketAddrV6::new(Ipv6Addr::LOCALHOST, 9090, 0, 0);

    // 2. From (IpAddr, u16) or ([u8; 4], u16) tuples via From/Into
    let from_tuple = SocketAddr::from(([192, 168, 0, 10], 5432));
    let from_ip_pair = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 3000);

    // 3. Parsing from string literals
    let parsed_v4: SocketAddr = "127.0.0.1:8080".parse().unwrap();
    let parsed_v6: SocketAddr = "[2001:db8::1]:443".parse().unwrap();

    assert_eq!(SocketAddr::V4(v4), parsed_v4);
    println!("v4:         {v4}");
    println!("v6:         {v6}");
    println!("from_tuple: {from_tuple}");
    println!("from_pair:  {from_ip_pair}");
    println!("parsed_v6:  ip={}, port={}", parsed_v6.ip(), parsed_v6.port());
}
v4:         127.0.0.1:8080
v6:         [::1]:9090
from_tuple: 192.168.0.10:5432
from_pair:  0.0.0.0:3000
parsed_v6:  ip=2001:db8::1, port=443

Mutating IP and Port Fields In Place

SocketAddr, SocketAddrV4, and SocketAddrV6 provide .set_ip(...) and .set_port(...) methods for updating an endpoint without reconstructing the entire value:

use std::net::{Ipv4Addr, SocketAddr};

fn main() {
    let mut addr: SocketAddr = "0.0.0.0:80".parse().unwrap();
    println!("Initial: {addr}");

    addr.set_ip(Ipv4Addr::LOCALHOST.into());
    addr.set_port(8443);
    println!("Updated: {addr}");
}
Initial: 0.0.0.0:80
Updated: 127.0.0.1:8443

Handling AddrParseError

Parsing a string via .parse::<SocketAddr>() performs syntactic parsing only—it never performs a DNS network lookup. Passing a hostname like "localhost:8080" to .parse::<SocketAddr>() returns Err(AddrParseError) (use ToSocketAddrs when DNS resolution is desired):

use std::net::SocketAddr;

fn main() {
    let bad_inputs = [
        "localhost:8080",   // Hostname, not an IP literal
        "::1:8080",         // Missing brackets around IPv6 address
        "127.0.0.1:99999",  // Port exceeds u16::MAX (65535)
    ];

    for input in bad_inputs {
        let res = input.parse::<SocketAddr>();
        println!("{input:<18} -> is_err: {}", res.is_err());
    }
}
localhost:8080     -> is_err: true
::1:8080           -> is_err: true
127.0.0.1:99999    -> is_err: true