Loading pageā¦
Rust walkthroughs
Loading pageā¦
bytes::BufMut extend Vec<u8> for efficient buffer writing patterns?BufMut is a trait that abstracts buffer writing operations, providing methods for writing various data types directly into a byte buffer without the overhead of individual bounds checks. While Vec<u8> requires manual reserve/resize management and forces you to convert types to bytes yourself, BufMut implementations like BytesMut track remaining capacity internally, provide specialized methods for each numeric type, and enable advanced patterns like zero-copy writes and efficient chunk management. The key efficiency gains come from: pre-validated capacity that eliminates per-write bounds checks, direct memory access for writing typed values, and the ability to work with both contiguous and non-contiguous underlying storage.
fn vec_writing_baseline() {
let mut buffer = Vec::new();
// Manual reserve for capacity
buffer.reserve(1024);
// Writing a u32 requires manual conversion
let value: u32 = 0x12345678;
buffer.extend_from_slice(&value.to_be_bytes());
// Writing multiple values
for i in 0..100 {
buffer.extend_from_slice(&(i as u32).to_be_bytes());
}
// Each extend_from_slice checks bounds
// Each to_be_bytes() creates a temporary array
}Writing to Vec<u8> requires manual capacity management and type-to-bytes conversion for each write.
use bytes::BufMut;
fn bufmut_basic() {
let mut buffer = bytes::BytesMut::new();
// Reserve capacity upfront
buffer.reserve(1024);
// Write typed values directly
buffer.put_u32(0x12345678); // Big-endian by default
buffer.put_u32_le(0x12345678); // Little-endian
// Write multiple values efficiently
for i in 0..100 {
buffer.put_u32(i);
}
// No intermediate byte arrays
// No per-write bounds checks after initial reserve
}BufMut provides typed methods that write directly into the buffer's memory.
use bytes::BufMut;
fn bounds_check_elimination() {
let mut buffer = bytes::BytesMut::with_capacity(1000);
// Vec<u8> approach: each write checks capacity
let mut vec = Vec::with_capacity(1000);
for i in 0..100 {
// Each extend_from_slice:
// 1. Checks if there's enough capacity
// 2. Potentially reallocates
// 3. Copies bytes
vec.extend_from_slice(&i.to_be_bytes());
}
// BufMut approach: chunk writes without checks
let mut buf = bytes::BytesMut::with_capacity(1000);
// Has_remaining() check once, then unchecked writes
for i in 0..100 {
buf.put_u64(i);
}
// When you know capacity is sufficient, use unchecked writes
let mut buf = bytes::BytesMut::with_capacity(100);
unsafe {
// Caller guarantees there's enough space
for i in 0..10 {
buf.put_u64_unchecked(i);
}
}
}BufMut can skip bounds checks when capacity is pre-validated, improving performance.
use bytes::BufMut;
fn typed_writing_methods() {
let mut buf = bytes::BytesMut::with_capacity(256);
// Integers - big endian (network byte order) by default
buf.put_u8(0xFF);
buf.put_i8(-128);
buf.put_u16(0x1234);
buf.put_i16(-1000);
buf.put_u32(0x12345678);
buf.put_i32(-100000);
buf.put_u64(0x123456789ABCDEF0);
buf.put_i64(-10000000000);
buf.put_u128(0x123456789ABCDEF0123456789ABCDEF0);
buf.put_i128(-100000000000000000000);
// Little-endian variants
buf.put_u16_le(0x1234);
buf.put_u32_le(0x12345678);
buf.put_u64_le(0x123456789ABCDEF0);
// Floating point
buf.put_f32(3.14159);
buf.put_f64(3.14159265358979);
buf.put_f32_le(3.14159);
buf.put_f64_le(3.14159265358979);
// Bytes and slices
buf.put_slice(b"hello world");
buf.put_bytes(0xAB, 10); // Write 0xAB ten times
}BufMut provides methods for every primitive type with explicit endianness.
use bytes::{BufMut, BytesMut, Bytes};
fn bytesmut_features() {
// BytesMut: the primary BufMut implementation
let mut buf = BytesMut::with_capacity(1024);
// Write some data
buf.put_u32(0x12345678);
buf.put_slice(b"hello");
// Split the buffer - zero-copy extraction
let written = buf.split();
// written: BytesMut containing the written data
// buf: Empty BytesMut ready for more writes
println!("Written: {:?}", written);
println!("Remaining buffer: {:?}", buf);
// This enables efficient producer/consumer patterns
}
fn split_pattern() {
// Common pattern for protocol implementations
let mut buf = BytesMut::with_capacity(4096);
loop {
// Write data into buffer
write_message(&mut buf);
// Split off complete messages
while buf.len() >= MESSAGE_HEADER_SIZE {
let len = parse_message_length(&buf);
if buf.len() < len {
break;
}
// Zero-copy message extraction
let message = buf.split_to(len);
process_message(message);
}
}
}
fn write_message(buf: &mut BytesMut) {
buf.put_u32(42); // Message header
buf.put_slice(b"payload");
}
fn parse_message_length(buf: &BytesMut) -> usize {
4 + 7 // Header + payload
}
fn process_message(msg: BytesMut) {
// Process without copying
}BytesMut provides split() operations for zero-copy buffer management.
use bytes::BufMut;
fn capacity_tracking() {
let mut buf = bytes::BytesMut::with_capacity(100);
// Check remaining capacity
println!("Remaining: {}", buf.remaining_mut());
// BufMut tracks remaining capacity internally
// No need to manually check len() vs capacity()
// Write data
buf.put_slice(&[0u8; 50]);
println!("Remaining after write: {}", buf.remaining_mut());
// Advance: mark bytes as written without actually writing
// Useful when you write directly to memory
unsafe {
// Get raw pointer to unwritten region
let ptr = buf.chunk_mut().as_mut_ptr();
// Write directly to memory (e.g., via FFI)
// std::ptr::write_bytes(ptr, 0xAB, 20);
// Tell BufMut we wrote 20 bytes
buf.advance_mut(20);
}
}
fn chunk_mut_pattern() {
use bytes::BufMut;
let mut buf = bytes::BytesMut::with_capacity(100);
unsafe {
// Access the unwritten portion directly
let chunk = buf.chunk_mut();
// chunk is a &mut [MaybeUninit<u8>]
// You can write to it without initializing
// After writing, advance the cursor
buf.advance_mut(10);
}
}BufMut tracks remaining capacity and provides unsafe methods for direct memory access.
use bytes::BufMut;
fn resizing_behavior() {
let mut buf = bytes::BytesMut::new();
println!("Initial capacity: {}", buf.capacity());
// Automatic resizing on write
buf.put_u64(42);
println!("After u64: capacity {}", buf.capacity());
// Writing more than capacity triggers resize
buf.put_slice(&[0u8; 1000]);
println!("After 1000 bytes: capacity {}", buf.capacity());
// With reserve, you control when allocation happens
let mut buf2 = bytes::BytesMut::new();
buf2.reserve(10000);
println!("After reserve: capacity {}", buf2.capacity());
// Now writes don't trigger reallocation
buf2.put_slice(&[0u8; 5000]);
println!("After 5000 bytes: capacity {}", buf2.capacity());
}BytesMut automatically resizes like Vec, but you can pre-reserve capacity.
use bytes::{BufMut, BytesMut, Bytes};
fn zero_copy_patterns() {
let mut buf = BytesMut::with_capacity(1024);
// Write some data
buf.put_slice(b"Hello, ");
buf.put_slice(b"World!");
// split() returns the written portion without copying
let data: Bytes = buf.split().freeze();
// data references the original allocation
// No copy was made!
// freeze() converts BytesMut to Bytes (immutable, shareable)
// Multiple readers can share the same Bytes
let reader1 = data.clone();
let reader2 = data.clone();
// Still no copies - reference counted
// The original buf can be reused
buf.put_slice(b"Next message");
}
fn efficient_concatenation() {
let mut buf = BytesMut::with_capacity(100);
buf.put_slice(b"Header: ");
buf.put_u32(0x12345678);
buf.put_slice(b" Data: ");
buf.put_slice(b"payload content here");
// All written contiguously in one buffer
// No intermediate allocations
let message = buf.split().freeze();
// message is a single contiguous Bytes
}BytesMut::split() and freeze() enable zero-copy data extraction and sharing.
use bytes::BufMut;
fn vec_pattern() {
// Vec<u8> pattern
let mut vec = Vec::with_capacity(100);
// Write header
vec.extend_from_slice(b"HEAD");
// Write length (must convert to bytes)
let len: u32 = 42;
vec.extend_from_slice(&len.to_be_bytes());
// Write payload
vec.extend_from_slice(b"payload");
// Converting to shared reference requires clone or Arc
let shared: Vec<u8> = vec.clone(); // Full copy!
}
fn bufmut_pattern() {
// BufMut pattern
let mut buf = bytes::BytesMut::with_capacity(100);
// Write header
buf.put_slice(b"HEAD");
// Write length (no intermediate array)
buf.put_u32(42);
// Write payload
buf.put_slice(b"payload");
// Convert to shared reference without copy
let shared: bytes::Bytes = buf.split().freeze(); // Zero copy!
}BufMut avoids intermediate allocations and enables zero-copy sharing.
use bytes::BufMut;
// You can implement BufMut for your own buffer types
struct LogBuffer {
data: Vec<u8>,
written: usize,
}
impl BufMut for LogBuffer {
fn remaining_mut(&self) -> usize {
self.data.capacity() - self.written
}
unsafe fn advance_mut(&mut self, cnt: usize) {
self.written += cnt;
}
fn chunk_mut(&mut self) -> &mut bytes::buf::UninitSlice {
// Safety: we're returning a mutable slice to unwritten memory
unsafe {
let ptr = self.data.as_mut_ptr().add(self.written);
let len = self.data.capacity() - self.written;
bytes::buf::UninitSlice::from_raw_parts_mut(ptr, len)
}
}
}
impl LogBuffer {
fn new(capacity: usize) -> Self {
Self {
data: vec![0; capacity],
written: 0,
}
}
fn as_slice(&self) -> &[u8] {
&self.data[..self.written]
}
}
fn custom_bufmut() {
let mut log = LogBuffer::new(100);
log.put_u32(0x12345678);
log.put_slice(b"log entry");
println!("Written: {:?}", log.as_slice());
}BufMut can be implemented for custom buffer types with specific requirements.
use bytes::BufMut;
// For async I/O, BytesMut implements IoBufMut (with tokio feature)
#[cfg(feature = "tokio")]
fn async_io_pattern() {
use bytes::BytesMut;
use tokio::io::AsyncWriteExt;
async fn write_message(mut writer: impl AsyncWriteExt + Unpin) {
let mut buf = BytesMut::with_capacity(1024);
// Build message in buffer
buf.put_u32(42); // Header
buf.put_slice(b"Hello, async world!");
// Write directly from buffer
// writer.write_buf(&mut buf).await.unwrap();
}
}BytesMut integrates with async I/O for efficient buffer management.
use bytes::{BufMut, BytesMut};
struct Message {
msg_type: u8,
flags: u8,
payload: Vec<u8>,
}
impl Message {
fn encode(&self, buf: &mut BytesMut) {
// Reserve space for header + length + payload
buf.reserve(4 + 4 + self.payload.len());
// Write header
buf.put_u8(self.msg_type);
buf.put_u8(self.flags);
buf.put_u16_be(self.payload.len() as u16);
// Write payload
buf.put_slice(&self.payload);
}
fn decode(buf: &mut BytesMut) -> Option<Self> {
// Check if we have enough bytes for header
if buf.len() < 4 {
return None;
}
// Peek at length (don't consume)
let len = u16::from_be_bytes([buf[2], buf[3]]) as usize;
// Check if we have full message
if buf.len() < 4 + len {
return None;
}
// Now consume
let msg_type = buf.get_u8();
let flags = buf.get_u8();
let _len = buf.get_u16();
let payload = buf.split_to(len).to_vec();
Some(Self { msg_type, flags, payload })
}
}
fn protocol_pattern() {
let mut buf = BytesMut::new();
// Encode messages
let msg1 = Message {
msg_type: 1,
flags: 0,
payload: b"hello".to_vec(),
};
msg1.encode(&mut buf);
let msg2 = Message {
msg_type: 2,
flags: 0,
payload: b"world".to_vec(),
};
msg2.encode(&mut buf);
// Decode messages
while let Some(msg) = Message::decode(&mut buf) {
println!("Message type: {}, payload: {:?}", msg.msg_type, msg.payload);
}
}BytesMut with BufMut methods provides a natural API for protocol encoding/decoding.
use bytes::BufMut;
fn memory_efficiency() {
// Vec<u8> approach with intermediate allocations
let mut vec = Vec::new();
// Each to_be_bytes() creates a temporary [u8; 8]
for i in 0..1000 {
vec.extend_from_slice(&(i as u64).to_be_bytes());
}
// 1000 temporary [u8; 8] arrays created
// BufMut approach: direct write
let mut buf = bytes::BytesMut::with_capacity(8000);
for i in 0..1000 {
buf.put_u64(i);
}
// No intermediate arrays
// Direct memory write
// Vec approach after reserve
let mut vec2 = Vec::with_capacity(8000);
for i in 0..1000 {
vec2.extend_from_slice(&(i as u64).to_be_bytes());
}
// Still has intermediate arrays, but no reallocations
}
fn benchmark_comparison() {
use std::time::Instant;
let iterations = 100_000;
// Vec<u8>
let start = Instant::now();
let mut vec = Vec::with_capacity(iterations * 8);
for i in 0..iterations {
vec.extend_from_slice(&(i as u64).to_be_bytes());
}
let vec_time = start.elapsed();
// BytesMut
let start = Instant::now();
let mut buf = bytes::BytesMut::with_capacity(iterations * 8);
for i in 0..iterations {
buf.put_u64(i);
}
let buf_time = start.elapsed();
println!("Vec<u8>: {:?}", vec_time);
println!("BytesMut: {:?}", buf_time);
// BytesMut is typically faster due to:
// 1. No intermediate byte arrays
// 2. Fewer bounds checks
// 3. Direct memory writes
}BufMut eliminates intermediate allocations and reduces bounds checking overhead.
use bytes::BytesMut;
use bytes::BufMut;
fn tokio_integration() {
// BytesMut works well with Tokio's async I/O
// (requires tokio feature in bytes crate)
// let mut buf = BytesMut::with_capacity(4096);
//
// // Read into buffer
// let n = async_stdin.read_buf(&mut buf).await?;
//
// // Process data
// while buf.has_remaining() {
// process_byte(buf.get_u8());
// }
}
fn prost_integration() {
// Protocol buffers with bytes::BufMut
// prost uses BufMut for encoding
// let mut buf = BytesMut::new();
// my_message.encode(&mut buf).unwrap();
//
// // buf now contains the encoded protobuf
// let bytes = buf.freeze();
}BytesMut integrates seamlessly with the Rust async ecosystem.
| Feature | Vec<u8> | BufMut (BytesMut) |
|---------|-----------|---------------------|
| Typed writes | Manual (to_be_bytes()) | Built-in (put_u32, etc.) |
| Bounds checking | Every extend_from_slice | Once per operation |
| Zero-copy extraction | Clone required | split() + freeze() |
| Shared ownership | Arc<Vec<u8>> or clone | Bytes (reference counted) |
| Endianness | Manual | Built-in (_le, _be suffixes) |
| Direct memory access | as_mut_ptr() + unsafe | chunk_mut() + advance_mut() |
| Capacity tracking | len() vs capacity() | remaining_mut() |
| Async I/O integration | Manual buffer management | read_buf, write_buf |
BufMut extends the basic Vec<u8> with efficient, typed, and zero-copy buffer writing capabilities:
Key advantages over Vec<u8>:
put_u32, put_f64, etc. write directly without intermediate byte arrayssplit() and freeze() create shareable references without copyingput_u32_be and put_u32_le methodschunk_mut() and advance_mut() for FFI and custom writesWhen to use BufMut:
When Vec is sufficient:
Key insight: BufMut transforms buffer writing from a sequence of "convert to bytes then copy" operations into direct typed writes, eliminating both the intermediate byte arrays and the redundant bounds checks. Combined with BytesMut's split() functionality, this enables patterns where data is written once and then shared across multiple readers without any copiesāa critical capability for high-performance network servers and protocol implementations.