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.