How to Build a TCP Client in Rust

To build a TCP client in Rust, use the standard library's `std::net::TcpStream` to establish a connection and then wrap it in a `BufReader` and `BufWriter` for efficient buffered I/O.

To build a TCP client in Rust, use the standard library's std::net::TcpStream to establish a connection and then wrap it in a BufReader and BufWriter for efficient buffered I/O. You handle connection errors with ? propagation and use read_line or read_exact to process incoming data.

Here is a complete, practical example that connects to a server, sends a message, and reads the response:

use std::io::{self, BufRead, BufReader, Write};
use std::net::TcpStream;

fn main() -> io::Result<()> {
    // Replace with your target server address
    let addr = "127.0.0.1:8080";
    
    // Establish the TCP connection
    let stream = TcpStream::connect(addr)?;
    
    // Wrap the stream for buffered reading and writing
    let mut reader = BufReader::new(stream.try_clone()?);
    let mut writer = BufWriter::new(stream);

    // Send a message
    let message = "Hello, server!\n";
    writer.write_all(message.as_bytes())?;
    writer.flush()?;

    // Read the response
    let mut response = String::new();
    reader.read_line(&mut response)?;

    println!("Server responded: {}", response.trim());
    Ok(())
}

If you need to handle multiple messages or a long-running session, wrap the stream in a loop. Note that TcpStream is not automatically cloned, so you must call try_clone() if you need separate reader and writer handles, or simply use the same stream instance for both operations if you manage the buffer carefully.

For a more robust application, consider using the tokio runtime for asynchronous non-blocking I/O, which is the standard for high-performance Rust network services. The async version looks like this:

use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpStream;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let stream = TcpStream::connect("127.0.0.1:8080").await?;
    let (mut reader, mut writer) = stream.into_split();
    let mut reader = BufReader::new(reader);

    writer.write_all(b"Hello, async server!\n").await?;
    writer.flush().await?;

    let mut line = String::new();
    reader.read_line(&mut line).await?;
    println!("Response: {}", line);

    Ok(())
}

Remember to add tokio = { version = "1", features = ["full"] } to your Cargo.toml for the async example. Always handle errors explicitly; the ? operator propagates io::Result errors up to main, which is the idiomatic way to handle connection failures in Rust.