Loading page…
Rust walkthroughs
Loading page…
bytes::BufMut enable efficient buffer writing without reallocation?bytes::BufMut is a trait for writing bytes into a buffer with automatic growth management, allowing efficient appends without manual reallocation tracking. Unlike Vec<u8> which grows by powers of two and may reallocate unpredictably, BufMut implementations like BytesMut provide more control over allocation strategy and avoid copies when possible. The key advantage is the remaining_mut() method that reports available capacity, and the advance_mut() method that commits written bytes without moving data. This enables patterns like reserving capacity once, writing multiple times, and growing only when necessary—all while tracking the valid written portion separately from capacity.
use bytes::{BufMut, BytesMut};
fn main() {
let mut buf = BytesMut::new();
// Write bytes - buffer grows automatically
buf.put_slice(b"hello");
buf.put_slice(b" ");
buf.put_slice(b"world");
// Convert to Bytes (zero-copy possible)
let bytes = buf.freeze();
println!("{}", String::from_utf8_lossy(&bytes));
}BufMut::put_slice writes bytes, growing the buffer if necessary.
use bytes::{BufMut, BytesMut};
fn main() {
// Vec<u8> approach - manual growth
let mut vec = Vec::new();
vec.write_all(b"hello").unwrap(); // Would need std::io::Write
vec.extend_from_slice(b"world");
// BufMut approach - automatic growth with tracking
let mut buf = BytesMut::new();
buf.put_slice(b"hello");
buf.put_slice(b"world");
// Key difference: BufMut tracks remaining capacity
println!("Vec capacity: {}", vec.capacity());
println!("BufMut remaining: {}", buf.remaining_mut());
}BufMut tracks remaining capacity explicitly.
use bytes::{BufMut, BytesMut};
fn main() {
let mut buf = BytesMut::with_capacity(64);
println!("Initial remaining: {}", buf.remaining_mut());
// remaining_mut() returns how many more bytes can be written
buf.put_slice(b"hello");
println!("After write: {}", buf.remaining_mut());
// Decreased by 5 bytes
// Check before writing large amounts
if buf.remaining_mut() >= 1024 {
buf.put_slice(&[0u8; 1024]);
} else {
// Reserve more space first
buf.reserve(1024);
buf.put_slice(&[0u8; 1024]);
}
}remaining_mut() lets you check capacity before writing.
use bytes::{BufMut, BytesMut};
fn main() {
let mut buf = BytesMut::new();
// Reserve capacity for known size
buf.reserve(1024);
// Now we can write without triggering growth
println!("Reserved remaining: {}", buf.remaining_mut());
buf.put_slice(&[0u8; 512]);
println!("After partial write: {}", buf.remaining_mut());
// reserve() grows the buffer but doesn't clear existing data
buf.reserve(2048);
println!("After more reserve: {}", buf.remaining_mut());
}reserve() pre-allocates space to avoid incremental growth.
use bytes::{BufMut, BytesMut};
use std::io::{self, Write};
fn main() -> io::Result<()> {
let mut buf = BytesMut::with_capacity(64);
// Get direct access to uninitialized memory
// This is unsafe because you must initialize the bytes
unsafe {
let remaining = buf.remaining_mut();
let dst = buf.bytes_mut();
// Write directly into buffer
let written = {
let mut cursor = io::Cursor::new(dst);
write!(cursor, "Hello, world!")?;
cursor.position() as usize
};
// Commit the written bytes
buf.advance_mut(written);
}
println!("Written: {}", String::from_utf8_lossy(&buf));
Ok(())
}advance_mut commits bytes written directly into the buffer.
use bytes::{BufMut, BytesMut, BigEndian, LittleEndian};
fn main() {
let mut buf = BytesMut::new();
// Integers in big-endian
buf.put_u16(0x1234);
buf.put_u32(0x12345678);
buf.put_u64(0x1234567890ABCDEF);
// Integers in little-endian
buf.put_u16_le(0x1234);
buf.put_u32_le(0x12345678);
buf.put_u64_le(0x1234567890ABCDEF);
// Floats
buf.put_f32(3.14159);
buf.put_f64(2.71828);
// Bytes
buf.put_u8(0xFF);
buf.put_i8(-1);
println!("Buffer length: {}", buf.len());
}BufMut provides type-safe methods for writing integers and floats.
use bytes::{BufMut, BytesMut, Buf};
fn main() {
let mut buf = BytesMut::new();
// put_slice for byte slices
buf.put_slice(b"hello");
// put for other Buf types
let other = BytesMut::from("world");
buf.put(other);
// put_bytes for repeating
buf.put_bytes(b'-', 10);
println!("{}", String::from_utf8_lossy(&buf));
}put works with any type implementing Buf.
use bytes::{BufMut, BytesMut};
fn main() {
let mut buf = BytesMut::new();
// First growth
buf.put_slice(&[0u8; 100]);
println!("After 100: capacity={}, remaining={}",
buf.capacity(), buf.remaining_mut());
// Second growth
buf.put_slice(&[0u8; 100]);
println!("After 200: capacity={}, remaining={}",
buf.capacity(), buf.remaining_mut());
// BytesMut grows as needed, typically doubling
// But it can also reuse allocated space after split
// Split off consumed portion
let _consumed = buf.split();
// Now buf is empty but may have reserved capacity
println!("After split: capacity={}, remaining={}",
buf.capacity(), buf.remaining_mut());
}BytesMut grows automatically but can reuse capacity.
use bytes::{BufMut, BytesMut};
fn main() {
let mut buf = BytesMut::new();
buf.put_slice(b"hello");
buf.put_slice(b"world");
// split() returns the filled portion and clears buf
let filled = buf.split();
// buf is now empty, but capacity may be retained
buf.put_slice(b"next");
// May reuse capacity from before
// freeze() converts to immutable Bytes (zero-copy)
let bytes = buf.freeze();
// buf is now consumed, bytes holds the data
// split_to() for partial split
let mut buf2 = BytesMut::new();
buf2.put_slice(b"hello world");
let prefix = buf2.split_to(5);
println!("Prefix: {:?}", String::from_utf8_lossy(&prefix));
println!("Remaining: {:?}", String::from_utf8_lossy(&buf2));
}split() and freeze() enable zero-copy extraction.
use bytes::{BufMut, BytesMut};
fn main() {
let mut buf = BytesMut::with_capacity(100);
// Write some data
buf.put_slice(b"hello");
// resize() changes length (pads with zeros)
buf.resize(20, 0);
println!("After resize: {:?}", &buf[..]);
// truncate() reduces length
buf.truncate(5);
println!("After truncate: {:?}", &buf[..]);
// clear() resets length to 0, keeps capacity
buf.clear();
println!("After clear: len={}, cap={}", buf.len(), buf.capacity());
}BufMut supports resizing operations while maintaining capacity.
use bytes::{BufMut, BytesMut};
// A wrapper that tracks write statistics
struct TrackingBufMut {
inner: BytesMut,
bytes_written: usize,
growths: usize,
}
impl TrackingBufMut {
fn new() -> Self {
Self {
inner: BytesMut::new(),
bytes_written: 0,
growths: 0,
}
}
fn write(&mut self, data: &[u8]) {
let remaining = self.inner.remaining_mut();
if remaining < data.len() {
self.growths += 1;
}
self.inner.put_slice(data);
self.bytes_written += data.len();
}
fn stats(&self) -> (usize, usize) {
(self.bytes_written, self.growths)
}
}
fn main() {
let mut buf = TrackingBufMut::new();
buf.write(b"hello");
buf.write(&[0u8; 100]);
let (written, growths) = buf.stats();
println!("Written: {}, Growth events: {}", written, growths);
}You can implement BufMut for custom types.
use bytes::{BufMut, BytesMut};
fn process_messages() {
let mut buf = BytesMut::with_capacity(1024);
// Process multiple messages reusing buffer
for i in 0..10 {
buf.clear(); // Reset length, keep capacity
buf.put_slice(format!("Message {}", i).as_bytes());
buf.put_u8(b'\n');
// Process the message
let message = buf.split();
println!("Processing: {:?}", String::from_utf8_lossy(&message));
}
// Only one allocation for 10 messages
}Reusing BytesMut avoids repeated allocations.
use bytes::{BufMut, BytesMut};
fn main() {
// Vec<u8> - manual management
let mut vec = Vec::new();
vec.reserve(100);
vec.extend_from_slice(b"hello");
vec.extend_from_slice(b"world");
// Converting to shared reference might copy
// BytesMut - automatic tracking
let mut buf = BytesMut::new();
buf.put_slice(b"hello");
buf.put_slice(b"world");
// freeze() gives Bytes without copy
let bytes = buf.freeze();
// Key differences:
// 1. BytesMut tracks written vs capacity
// 2. freeze() enables zero-copy sharing
// 3. split() returns consumed portion efficiently
// 4. remaining_mut() queries available space
}BytesMut is designed for network buffers with efficient sharing.
use bytes::{BufMut, BytesMut};
use std::io::IoSlice;
fn main() {
let mut buf1 = BytesMut::with_capacity(64);
let mut buf2 = BytesMut::with_capacity(64);
buf1.put_slice(b"First buffer");
buf2.put_slice(b"Second buffer");
// Convert to IoSlice for vectored I/O
let slices: Vec<IoSlice> = vec![
IoSlice::new(&buf1),
IoSlice::new(&buf2),
];
// Can use with write_vectored
println!("First: {:?}", String::from_utf8_lossy(&buf1));
println!("Second: {:?}", String::from_utf8_lossy(&buf2));
}BytesMut integrates with vectored I/O operations.
use bytes::{BufMut, BytesMut, Buf};
fn main() {
let mut buf = BytesMut::new();
// Write in chunks
let data = b"hello world, this is a longer message";
let chunk_size = 8;
for chunk in data.chunks(chunk_size) {
if buf.remaining_mut() < chunk.len() {
buf.reserve(chunk.len());
}
buf.put_slice(chunk);
}
println!("Written: {:?}", String::from_utf8_lossy(&buf));
}Write in chunks while managing capacity efficiently.
use bytes::{BufMut, BytesMut, BigEndian};
// A simple message: [length: u32][type: u8][payload: bytes]
fn encode_message(msg_type: u8, payload: &[u8]) -> BytesMut {
let total_len = 1 + payload.len(); // type + payload
let mut buf = BytesMut::with_capacity(4 + total_len);
// Length header
buf.put_u32::<BigEndian>(total_len as u32);
// Message type
buf.put_u8(msg_type);
// Payload
buf.put_slice(payload);
buf
}
fn main() {
let msg = encode_message(0x01, b"Hello, world!");
println!("Encoded {} bytes", msg.len());
// Can be sent directly over network
// Or shared via freeze() without copying
}BufMut is ideal for encoding network protocols.
use bytes::{BufMut, BytesMut};
fn build_http_response(status: u16, headers: &[(String, String)], body: &[u8]) -> BytesMut {
let mut buf = BytesMut::new();
// Status line
buf.put_slice(format!("HTTP/1.1 {} OK\r\n", status).as_bytes());
// Headers
for (name, value) in headers {
buf.put_slice(format!("{}: {}\r\n", name, value).as_bytes());
}
// Empty line
buf.put_slice(b"\r\n");
// Body
buf.put_slice(body);
buf
}
fn main() {
let response = build_http_response(
200,
&[
("Content-Type".to_string(), "text/plain".to_string()),
("Content-Length".to_string(), "5".to_string()),
],
b"Hello",
);
println!("{}", String::from_utf8_lossy(&response));
}Building responses with BufMut avoids repeated string allocations.
use bytes::{BufMut, BytesMut};
struct SmartBuffer {
buf: BytesMut,
growth_factor: usize,
min_capacity: usize,
}
impl SmartBuffer {
fn new() -> Self {
Self {
buf: BytesMut::new(),
growth_factor: 2,
min_capacity: 64,
}
}
fn ensure_capacity(&mut self, additional: usize) {
let remaining = self.buf.remaining_mut();
if remaining < additional {
// Grow by factor, but at least enough for additional
let current = self.buf.capacity();
let needed = self.buf.len() + additional;
let new_capacity = (needed * self.growth_factor)
.max(self.min_capacity);
self.buf.reserve(new_capacity - current);
}
}
fn write(&mut self, data: &[u8]) {
self.ensure_capacity(data.len());
self.buf.put_slice(data);
}
}
fn main() {
let mut smart = SmartBuffer::new();
smart.write(b"hello");
smart.write(&[0u8; 100]);
}Custom capacity strategies prevent allocation surprises.
use bytes::{BufMut, BytesMut, Bytes};
fn main() {
let mut buf = BytesMut::new();
buf.put_slice(b"hello");
// freeze() returns Bytes which is Clone + Send + Sync
let bytes: Bytes = buf.freeze();
// Can be sent to other threads
let bytes2 = bytes.clone();
let bytes3 = bytes.clone();
// Each clone references the same underlying data
println!("{:?}", bytes2);
println!("{:?}", bytes3);
}freeze() produces immutable, shareable Bytes.
use bytes::{BufMut, BytesMut, Buf, Bytes};
fn main() {
// Chain buffers without copying
let mut buf1 = BytesMut::new();
buf1.put_slice(b"hello ");
let mut buf2 = BytesMut::new();
buf2.put_slice(b"world");
// Create Bytes references
let b1: Bytes = buf1.freeze();
let b2: Bytes = buf2.freeze();
// Chain (no copy)
let chain = b1.chain(b2);
println!("Chained: {:?}", chain.bytes());
// Copying only when necessary
let mut combined = BytesMut::with_capacity(b1.len() + b2.len());
combined.put_slice(&b1);
combined.put_slice(&b2);
println!("Combined: {:?}", String::from_utf8_lossy(&combined));
}Bytes can be chained without copying.
BufMut core concepts:
remaining_mut() - available capacityput_* methods - write typed datareserve() - pre-allocate capacityadvance_mut() - commit direct writessplit() - extract written datafreeze() - create immutable BytesEfficiency mechanisms:
remaining_mut() is insufficientsplit() returns filled portion without copyfreeze() creates shareable Bytes without copyclear() or split()When to use BufMut:
When Vec is sufficient:
Key insight: BufMut is about separating the concepts of "written length" and "capacity" while providing a path to immutable shared ownership. Vec<u8> conflates these—a slice of Vec either borrows the whole Vec (preventing mutation) or requires copying. BytesMut solves this by allowing split() to extract the written portion, leaving capacity behind for reuse, and freeze() to create a Bytes that can be cloned cheaply (reference counting, not copying). This design matches network programming patterns: write into a buffer, send the data (shared), and reuse the buffer for the next message. The remaining_mut() method enables checking capacity before writing, avoiding the "write then check" pattern that could leave partial writes.