Why Rust is the New King of Network Programming?
\n\nHave you ever written a network server in C or C++ and caught a segmentation fault due to a forgotten mutex? Or struggled with data races in Go, trying to understand why a goroutine "hung"? Rust solves these problems once and for all. Its ownership system and fearless concurrency model make network programming in Rust not only safe but also incredibly performant.
\n\nIn this article, we'll break down how to create TCP and UDP servers in Rust, using both the standard library (std::net) and the modern async stack based on Tokio. You'll see that Rust is not just a language for systems programming, but also an excellent tool for writing high-load network services.
Basics: std::net — TCP Echo Server in 5 Minutes
\n\nLet's start with a classic — an echo server. It accepts a TCP connection, reads data, and sends it back to the client. In Rust, this is surprisingly concise.
\n\nuse std::io::{Read, Write};\nuse std::net::{TcpListener, TcpStream};\nuse std::thread;\n\nfn handle_client(mut stream: TcpStream) {\n let mut buffer = [0; 1024];\n loop {\n match stream.read(&mut buffer) {\n Ok(0) => {\n println!("Client disconnected");\n break;\n }\n Ok(n) => {\n if let Err(e) = stream.write_all(&buffer[..n]) {\n eprintln!("Write error: {}", e);\n break;\n }\n }\n Err(e) => {\n eprintln!("Read error: {}", e);\n break;\n }\n }\n }\n}\n\nfn main() -> std::io::Result<()> {\n let listener = TcpListener::bind("127.0.0.1:7878")?;\n println!("Server started on port 7878");\n\n for stream in listener.incoming() {\n match stream {\n Ok(stream) => {\n thread::spawn(|| handle_client(stream));\n }\n Err(e) => {\n eprintln!("Connection error: {}", e);\n }\n }\n }\n Ok(())\n}\n\n\nWhat's happening here? TcpListener::bind creates a listening socket. The incoming() method returns an iterator over incoming connections. For each new client, we spawn a separate thread — thread::spawn. Inside handle_client, we read data into a buffer and send it back via write_all.
Important: Rust guarantees that TcpStream won't accidentally be used from two threads simultaneously — the compiler simply won't allow it. This is fearless concurrency.
Problems with This Approach
\n- \n
- Scaling: Each client is a separate thread. With 10,000 clients, the OS will simply choke. \n
- Resources: The default thread stack is 2 MB. 10,000 clients = 20 GB of memory. \n
Asynchronous programming comes to the rescue.
\n\nAsync Revolution: Tokio and async/await
\n\nRust does not have a built-in async runtime (like Go or Node.js). Instead, it provides the Future trait and the async/await keywords, and the choice of runtime is up to you. The most popular is Tokio. It's not just a library, it's a whole ecosystem: TCP/UDP, file I/O, timers, channels, and even its own scheduler.
Let's rewrite our echo server with Tokio.
\n\nTokio Echo Server
\nAdd to Cargo.toml:
[dependencies]\ntokio = { version = "1", features = ["full"] }\n\nNow the code:
\n\nuse tokio::io::{AsyncReadExt, AsyncWriteExt};\nuse tokio::net::TcpListener;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n