Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
105: SocketAddr and IP Address Types
I see this quite often when developers first move into network programming with Rust: they assume that std::net::IpAddr is the type they need for every single networking function. They'll try to pass an IP address directly into a TcpStream::connect call and then spend twenty minutes fighting the compiler, wondering why the trait bounds aren't being met.
An IP Address is Not a Connection Point
Here is the mistake in action. If you're trying to connect to a database or a web server, you might be tempted to do this:
use std::net::IpAddr;
use std::net::TcpStream;
fn main() {
// This is a valid IP address
let server_ip: IpAddr = "127.0.0.1".parse().unwrap();
// ERROR: This will not compile!
let stream = TcpStream::connect(server_ip).expect("Failed to connect");
}
The compiler will complain because TcpStream::connect doesn't just want to know which machine to talk to; it needs to know which process on that machine should receive the data. An IP address is like the street address of an apartment building. It gets you to the right building, but it doesn't tell you which apartment number to knock on. In networking, that "apartment number" is the port.
SocketAddr: Pairing the Address with the Port
To actually establish a connection, you need a SocketAddr. This type bundles an IpAddr together with a u16 port number. Rust provides SocketAddr as an enum that can represent either an IPv4 or an IPv6 socket.
If you have a string that includes the port, like "127.0.0.1:8080", you can parse it directly into a SocketAddr. I usually prefer this approach because it's concise and handles the internal splitting for you:
use std::net::SocketAddr;
use std::net::TcpStream;
fn main() {
// Note the port appended to the string
let address_str = "127.0.0.1:8080";
let socket_addr: SocketAddr = address_str.parse().expect("Invalid address format");
// Now this works perfectly
let _stream = TcpStream::connect(socket_addr).expect("Could not connect to server");
}
If your port is stored in a separate variable, you can construct a SocketAddr using the specific V4 or V6 types. For example, SocketAddr::V4(SocketAddrV4::new(ipv4_addr, port)). It's a bit more verbose, but it gives you total control when building addresses dynamically.
Handling IPv4 and IPv6 Dynamically with Enums
You'll notice that Rust uses enums for both IpAddr and SocketAddr. This is a design choice I really appreciate because it forces you to be explicit about the protocol you're using while still allowing your functions to be generic.
IpAddr: An enum ofV4(Ipv4Addr)andV6(Ipv6Addr). Use this when you only care about the identity of the host (e.g., checking a whitelist of allowed IPs).SocketAddr: An enum ofV4(SocketAddrV4)andV6(SocketAddrV6). Use this whenever you are actually sending or receiving packets.
When I'm writing a server that needs to support both protocols, I use pattern matching to handle the specifics of each. It prevents those annoying "it only works on my machine" bugs that happen when you accidentally hardcode IPv4 logic into a world that is rapidly moving toward IPv6.
use std::net::{IpAddr, Ipv4Addr};
fn analyze_address(addr: IpAddr) {
match addr {
IpAddr::V4(v4) => println!("Processing IPv4: {}. Is it local? {}", v4, v4.is_loopback()),
IpAddr::V6(v6) => println!("Processing IPv6: {}. This is a much longer string!", v6),
}
}
📋 Practical Task
Building a Multi-Protocol Address Validator
Your task is to write a function called validate_connection_string. This function should take a string slice &str and attempt to parse it into a SocketAddr.
The function should return a Result:
- If the string is a valid
SocketAddr, returnOk(()). - If the string is a valid
IpAddrbut is missing a port, return anErrwith the message"Missing port number". - If the string is completely invalid (neither an IP nor a Socket Address), return an
Errwith the message"Invalid address format".
Requirements:
- Use
std::net::SocketAddrandstd::net::IpAddr. - Use the
parse()method to attempt conversions. - Test your function with these three cases:
"127.0.0.1:8080"(Valid),"192.168.1.1"(Missing port), and"not-an-ip"(Invalid).
There are no comments for now.