Byte Streams and Stream Processing in Rust

This document explains how to work with byte streams in Rust, using the standard library's Read and Write traits to process data in chunks and manage buffered I/O operations.

Working with Byte Streams

Rust's standard library provides efficient methods for working with byte streams through the std::io::Read and std::io::Write traits. These traits allow reading from and writing to any source or sink that implements them, including files, network sockets, and pipes.

use std::io;
use std::io::Read;

fn main() -> io::Result<()> {
 let mut file = std::fs::File::open("data.bin")?;
 let mut buffer = [0; 1024];
 let bytes_read = file.read(&mut buffer)?;
 
 println!("Read {} bytes: {:?}", bytes_read, &buffer[..bytes_read]);
 Ok(())
}
 

Buffered I/O for Efficiency

For improved performance, use BufReader and BufWriter to add buffering to any Read or Write implementation.

use std::io::{BufRead, BufReader, Read};
use std::fs::File;

fn main() -> std::io::Result<()> {
 let file = File::open("data.bin")?;
 let reader = BufReader::new(file);
 
 let mut buffer = String::new();
 reader.read_line(&mut buffer)?;
 
 println!("First line: {}", buffer);
 Ok(())
}
 

Processing Data in Chunks

Read and process streams in arbitrary-sized chunks for memory efficiency, especially for large datasets or network streams:

use std::io::{BufReader, Read};
use std::fs::File;

fn main() -> Result<(), std::io::Error> {
 let file = File::open("large-data.bin")?;
 let mut reader = BufReader::new(file);
 let mut buffer = [0; 32768]; // 32KB chunks
 
 let mut total_bytes = 0;
 
 while let Ok(count) = reader.read(&mut buffer) {
 if count == 0 { break; }
 total_bytes += count;
 process_chunk(&buffer[..count]); // Custom processing
 }
 
 println!("Processed {} total bytes", total_bytes);
 Ok(())
}

fn process_chunk(data: &[u8]) {
 // Custom implementation to handle each chunk
}
 

Streaming Transformations

Use the io::copy function or the chain method to perform efficient stream transformations:

use std::io::{self, BufReader, BufWriter, Read, Write};
use std::fs::{File, OpenOptions};

fn main() -> io::Result<()> {
 let input = File::open("source.txt")?;
 let output = OpenOptions::new()
 .write(true)
 .create(true)
 .open("target.txt")?;
 
 let mut reader = BufReader::new(input);
 let mut writer = BufWriter::new(output);
 
 io::copy(&mut reader, &mut writer)?;
 Ok(())
}
 

Stream Combinators

Rust provides powerful stream combinators through the standard library for complex I/O patterns:

use std::io::BufRead;
use std::io::BufReader;
use std::fs::File;

fn main() -> std::io::Result<()> {
 let file = File::open("data.txt")?;
 let reader = BufReader::new(file);
 
 for line in reader.lines() {
 println!("Line: {}", line?);
 }
 Ok(())
}
 

Error Handling

Always handle stream errors with proper error propagation:

use std::io;
use std::fs::File;
use std::io::Read;

fn read_entire_file() -> std::io::Result<String> {
 let mut s = String::new();
 File::open("data.txt")?.read_to_string(&mut s)?;
 Ok(s)
}

fn main() {
 match read_entire_file() {
 Ok(contents) => println!("Read: {}", contents),
 Err(e) => eprintln!("Error reading file: {}", e),
 }
}
 

Key Concepts

Working with streams in Rust requires understanding these core concepts:

For asynchronous stream processing see the Asynchronous I/o section for tokio and async-std implementations of stream processing patterns.