How does bytes::BufMut::put_slice differ from put for writing byte slices into a buffer?

put_slice accepts a byte slice (&[u8]) and copies its contents into the buffer, while put is a generic method that can write any type implementing Buf into the buffer. The key distinction is that put_slice is specialized for byte slices and always performs a copy, whereas put can work with various Buf implementations including Bytes, BytesMut, and other buffer types—potentially using more efficient transfer mechanisms like reference counting. For simple byte slice operations, put_slice is more explicit and readable, but put offers broader functionality for transferring data between buffers.

Basic put_slice Usage

use bytes::BufMut;
 
fn basic_put_slice() {
    let mut buf = Vec::with_capacity(32);
    
    // put_slice accepts a byte slice and copies it into the buffer
    buf.put_slice(b"hello");
    buf.put_slice(b" world");
    
    assert_eq!(&buf[..], b"hello world");
}
 
fn put_slice_with_bytesmut() {
    use bytes::BytesMut;
    
    let mut buf = BytesMut::with_capacity(32);
    
    // put_slice works with BytesMut too
    buf.put_slice(b"hello");
    buf.put_slice(b" ");
    buf.put_slice(b"world");
    
    assert_eq!(&buf[..], b"hello world");
}

put_slice takes a &[u8] and copies the bytes into the buffer.

Basic put Usage

use bytes::{BufMut, Bytes, BytesMut};
 
fn basic_put() {
    let mut buf = BytesMut::with_capacity(32);
    
    // put is generic and accepts types implementing Buf
    // For slices, put behaves similarly to put_slice:
    buf.put(b"hello".as_slice());
    
    // But put can also accept other Buf types:
    let bytes = Bytes::from(" world");
    buf.put(bytes);  // Takes Bytes, not &[u8]
    
    assert_eq!(&buf[..], b"hello world");
}

put accepts any type implementing Buf, including Bytes, BytesMut, and slices.

Method Signatures

use bytes::BufMut;
 
// Simplified signatures from the BufMut trait:
 
// put_slice: Specialized for byte slices
// fn put_slice(&mut self, src: &[u8])
// - Always copies bytes from src into self
// - Explicitly for byte slices
// - No reference counting or special handling
 
// put: Generic over Buf
// fn put<T>(&mut self, src: T) where T: Buf
// - Accepts any type implementing Buf
// - Can use optimized implementations
// - For Bytes/BytesMut, may use reference counting
 
fn signature_comparison() {
    let mut buf = Vec::with_capacity(32);
    
    // put_slice: explicit slice parameter
    buf.put_slice(b"data");
    
    // put: generic Buf parameter
    buf.put(Bytes::from("more data"));
}

put_slice is specialized for slices; put is generic over Buf.

Copy vs Reference Semantics

use bytes::{BufMut, Bytes, BytesMut};
 
fn copy_vs_reference() {
    let mut buf = BytesMut::with_capacity(32);
    
    // put_slice always copies bytes
    let data = b"hello";
    buf.put_slice(data);
    // 'data' is copied into buf, no reference to original
    
    // put with Bytes can use reference counting
    let bytes = Bytes::from(" world");
    buf.put(bytes);
    // For Bytes, this may reference-count instead of copying
    // Depends on BytesMut implementation
    
    // put with BytesMut might use different strategies
    let mut other = BytesMut::from("!");
    buf.put(other);
}
 
fn put_slice_always_copies() {
    use bytes::BytesMut;
    
    let mut buf = BytesMut::with_capacity(32);
    
    // put_slice never shares memory
    let original = Bytes::from("data");
    buf.put_slice(&original);
    // original is copied into buf
    
    // In contrast:
    let mut buf2 = BytesMut::with_capacity(32);
    buf2.put(original);
    // put might use reference counting for Bytes
}

put_slice always copies; put with Bytes might use reference counting.

Working with Bytes Type

use bytes::{BufMut, Bytes, BytesMut};
 
fn put_with_bytes() {
    let mut buf = BytesMut::with_capacity(32);
    
    // Bytes implements Buf, so it works with put
    let data = Bytes::from("hello");
    buf.put(data);
    
    // Cannot use put_slice with Bytes directly:
    // buf.put_slice(data);  // Error: Bytes is not &[u8]
    
    // Must slice it first:
    buf.put_slice(&data);
    
    // put is more ergonomic when you have Bytes
}
 
fn efficiency_comparison() {
    let mut buf = BytesMut::with_capacity(32);
    
    // Using put with Bytes - may be optimized
    let data = Bytes::from("hello world");
    buf.put(data);
    
    // Using put_slice with Bytes - must slice first
    let data = Bytes::from("hello world");
    buf.put_slice(&data);  // Slices and copies
    
    // For large Bytes objects, put may be more efficient
    // as it can potentially use reference counting
}

put is more natural when working with Bytes objects.

Working with BytesMut

use bytes::{BufMut, BytesMut};
 
fn put_with_bytesmut() {
    let mut dest = BytesMut::with_capacity(32);
    let mut source = BytesMut::from("hello world");
    
    // BytesMut implements Buf, so it works with put
    dest.put(source);
    
    // But you might want to transfer data:
    // put consumes the source's Buf implementation
    
    // put_slice is explicit about copying
    dest.put_slice(&source);
}
 
fn chaining_with_put() {
    let mut buf = BytesMut::with_capacity(32);
    
    // put can chain Buf types nicely
    buf.put(Bytes::from("hello"));
    buf.put(BytesMut::from(" "));
    buf.put(Bytes::from("world"));
    
    // put_slice is more explicit for slice operations
    buf.put_slice(b"hello");
    buf.put_slice(b" ");
    buf.put_slice(b"world");
}

Both work with BytesMut, but put chains better with Buf types.

Performance Characteristics

use bytes::{BufMut, Bytes, BytesMut};
 
fn performance_comparison() {
    let mut buf = BytesMut::with_capacity(1024);
    
    // put_slice: straightforward copy
    // - Reads each byte from source
    // - Writes each byte to destination
    // - Linear in size of slice
    buf.put_slice(b"hello world");
    
    // put with Bytes: potentially optimized
    // - May use reference counting (no copy)
    // - May share underlying storage
    // - Depends on BytesMut implementation
    let data = Bytes::from("hello world");
    buf.put(data);
    
    // For small data, difference is negligible
    // For large data, put with Bytes may be faster
}
 
fn small_vs_large() {
    let mut buf = BytesMut::new();
    
    // Small data: put_slice is fine
    buf.put_slice(b"small");
    
    // Large data: consider using put with Bytes
    let large = Bytes::from(vec![0u8; 1_000_000]);
    buf.put(large);
}

put_slice copies; put might avoid copying for Bytes.

When to Use put_slice

use bytes::{BufMut, BytesMut};
 
fn when_put_slice() {
    let mut buf = BytesMut::with_capacity(32);
    
    // Use put_slice when:
    // 1. You have a byte slice
    // 2. You want explicit copy semantics
    // 3. Readability is important
    
    // Clear intent: copying a slice
    buf.put_slice(b"content-type: text/plain\r\n");
    
    // From a slice source
    let data: &[u8] = &get_data();
    buf.put_slice(data);
    
    // When you don't want reference semantics
    let original = Bytes::from("data");
    buf.put_slice(&original);  // Guaranteed copy, no sharing
}
 
fn get_data() -> Vec<u8> {
    vec![1, 2, 3, 4]
}

Use put_slice when you have slices and want explicit copy semantics.

When to Use put

use bytes::{BufMut, Bytes, BytesMut};
 
fn when_put() {
    let mut buf = BytesMut::with_capacity(32);
    
    // Use put when:
    // 1. You have a Buf type (Bytes, BytesMut)
    // 2. You want potential optimization
    // 3. You're transferring between buffers
    
    // From Bytes
    let bytes = Bytes::from("data");
    buf.put(bytes);
    
    // From BytesMut
    let other = BytesMut::from("more data");
    buf.put(other);
    
    // When you want reference semantics possible
    // (implementation-dependent)
}
 
fn buf_types() {
    let mut buf = BytesMut::new();
    
    // Bytes implements Buf
    buf.put(Bytes::from("hello"));
    
    // BytesMut implements Buf
    buf.put(BytesMut::from(" world"));
    
    // &[u8] also implements Buf
    // But for slices, put_slice is clearer
    buf.put(b"!".as_slice());  // Works, but put_slice is more explicit
}

Use put when you have Buf types and want potential optimization.

Generic Code with BufMut

use bytes::{Buf, BufMut, BytesMut};
 
fn generic_buffer<B: BufMut>(buf: &mut B, data: &[u8]) {
    // put_slice works with any BufMut
    buf.put_slice(data);
}
 
fn generic_with_buf<B: BufMut, T: Buf>(buf: &mut B, source: T) {
    // put works with any BufMut and any Buf
    buf.put(source);
}
 
fn using_generics() {
    let mut bytes_mut = BytesMut::new();
    generic_buffer(&mut bytes_mut, b"hello");
    
    let mut vec: Vec<u8> = Vec::new();
    generic_buffer(&mut vec, b"world");
    
    // Both BytesMut and Vec<u8> implement BufMut
}

Both methods work generically with any BufMut implementation.

Reserving Capacity

use bytes::{BufMut, BytesMut};
 
fn capacity_handling() {
    let mut buf = BytesMut::new();
    
    // Both put_slice and put may need to grow the buffer
    // put_slice copies into available space, growing if needed
    buf.put_slice(b"hello");  // May allocate
    
    // Best practice: reserve capacity first
    let mut buf = BytesMut::with_capacity(1024);
    buf.put_slice(b"hello");  // No allocation needed
    
    // put also respects capacity
    buf.put(Bytes::from(" world"));  // May allocate if needed
}
 
fn chunk_appending() {
    let mut buf = BytesMut::with_capacity(32);
    
    // Multiple put_slice calls
    buf.put_slice(b"hello");
    buf.put_slice(b" ");
    buf.put_slice(b"world");
    
    // Equivalent single call
    buf.put_slice(b"hello world");
}

Both methods grow the buffer as needed; pre-allocate with with_capacity.

Error Handling

use bytes::{BufMut, BytesMut};
 
fn error_handling() {
    // Neither put_slice nor put return Result
    // Both panic on allocation failure
    
    let mut buf = BytesMut::new();
    
    // put_slice: panics if allocation fails
    buf.put_slice(b"data");
    
    // put: panics if allocation fails
    buf.put(Bytes::from("data"));
    
    // Both methods panic in the same scenarios
    // If you need fallible allocation, use reserve first
}
 
fn checked_allocation() {
    let mut buf = BytesMut::new();
    
    // Reserve capacity first (no panic)
    if buf.try_reserve(1024).is_ok() {
        buf.put_slice(b"safe data");
    }
}

Both methods panic on allocation failure; use try_reserve for fallible allocation.

Integration with Buf Trait

use bytes::{Buf, BufMut, Bytes, BytesMut};
 
fn buf_integration() {
    let mut buf = BytesMut::with_capacity(32);
    
    // put is part of the BufMut trait
    // It consumes any type implementing Buf
    // This creates a unified interface for buffer operations
    
    let bytes: Bytes = Bytes::from("hello");
    buf.put(bytes);  // bytes is consumed
    
    let mut other = BytesMut::from(" world");
    buf.put(other);  // other's contents are transferred
    
    // put_slice is a convenience for the common slice case
    buf.put_slice(b"!");
    
    // After put, the source may be empty (for BytesMut)
    // or just consumed (for Bytes)
}
 
fn buf_chain_example() {
    use bytes::buf::Chain;
    
    let mut buf = BytesMut::new();
    
    // Chain two Bufs together
    let a = Bytes::from("hello");
    let b = Bytes::from(" world");
    let chained = a.chain(b);
    
    // put can consume the chained Buf
    buf.put(chained);
    
    assert_eq!(&buf[..], b"hello world");
}

put integrates with the Buf ecosystem, supporting chaining and composition.

Complete Comparison Example

use bytes::{BufMut, Bytes, BytesMut};
 
fn complete_comparison() {
    let data: &[u8] = b"hello world";
    let bytes: Bytes = Bytes::from("hello world");
    
    // put_slice: explicit slice copy
    let mut buf1 = BytesMut::new();
    buf1.put_slice(data);
    buf1.put_slice(&bytes);  // Must deref Bytes to &[u8]
    
    // put: generic Buf
    let mut buf2 = BytesMut::new();
    // buf2.put(data);  // Error: &[u8] needs .as_slice() or Buf impl
    buf2.put(data.as_slice());  // Works but less clear than put_slice
    buf2.put(bytes);  // Bytes implements Buf
    
    // For &[u8]: put_slice is clearer
    // For Bytes/BytesMut: put is more natural
    
    // put_slice: always copies
    // put: may use reference counting for Bytes
}

Comparison Summary

use bytes::{BufMut, BytesMut};
 
fn comparison_table() {
    // | Aspect | put_slice | put |
    // |--------|-----------|-----|
    // | Input | &[u8] | impl Buf |
    // | Semantics | Always copy | May reference |
    // | Best for | Byte slices | Bytes/BytesMut |
    // | Clarity | Explicit slice | Generic Buf |
    // | Bytes efficiency | Copies | May optimize |
    // | BytesMut efficiency | Copies | May transfer |
}

Synthesis

Quick reference:

use bytes::{BufMut, Bytes, BytesMut};
 
fn quick_reference() {
    let mut buf = BytesMut::new();
    
    // put_slice: Specialized for byte slices
    // - Accepts &[u8]
    // - Always copies
    // - Clear intent for slice operations
    buf.put_slice(b"hello world");
    
    // put: Generic over Buf
    // - Accepts any Buf implementation
    // - May use optimization for Bytes
    // - Natural for Bytes/BytesMut
    buf.put(Bytes::from("hello world"));
    
    // Use put_slice when:
    // - You have a byte slice
    // - You want explicit copy semantics
    // - Clarity is important
    
    // Use put when:
    // - You have Bytes or BytesMut
    // - You want potential optimization
    // - Working with Buf types generically
    
    // For &[u8]: put_slice is preferred (more explicit)
    // For Bytes: put is preferred (more ergonomic)
}

Key insight: The distinction between put_slice and put reflects the design philosophy of the bytes crate—providing both explicit, predictable operations (put_slice) and generic, potentially-optimized operations (put). put_slice is the straightforward choice when you have a byte slice and want to copy it into a buffer; it always performs a copy and makes that behavior explicit in the API. put is more powerful, accepting any type implementing Buf, which enables efficient data movement between Bytes and BytesMut objects using reference counting or other optimizations. For code working primarily with Bytes and BytesMut, put is more natural and may offer performance benefits. For code working with slices or where explicit copy semantics are desired, put_slice provides clarity. The put method integrates with the broader Buf ecosystem, supporting chained buffers and other compositions, while put_slice focuses on the common case of copying a slice into a buffer.