bytes

repository·master·Indexed 25 days ago

https://github.com/tokio-rs/bytes

A utility library for working with bytes in Rust, providing efficient byte buffers (Bytes, BytesMut) and traits for reading and writing (Buf, BufMut). It includes adapters such as Chain for concatenating buffers, Limit for restricting writes, Take for limiting reads, and Reader/Writer for compatibility with std::io.

Tokens
7.5K
Snippets
23
Records
38
Agent score
79%

What's inside bytes

  1. Use bytes in a no_std environment

    master

    If you are working in a no_std environment, you must disable the default std feature.

    [dependencies]
    bytes = { version = "1", default-features = false }

    For platforms that do not support atomic CAS (such as thumbv6m), you must also enable the extra-platforms feature. Note that the Minimum Supported Rust Version (MSRV) when extra-platforms is enabled depends on the MSRV of the portable-atomic crate.

  2. Build documentation with feature gates

    master

    When building documentation for bytes, use the docsrs option to ensure that feature gates are correctly displayed. This requires a nightly Rust toolchain.

    RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc
  3. What is the `Bytes` type?

    master

    A Bytes instance is a cheaply cloneable and sliceable chunk of contiguous memory. It is designed for efficient, zero-copy operations, making it ideal for networking code where multiple components need to share access to the same underlying buffer without expensive allocations or copying.

    Key characteristics:

    • Cheap Cloning: Cloning a Bytes object increments a reference count (or is a no-op for static memory) rather than copying the data.
    • Slicing: You can create new Bytes handles that point to subsets of the original memory in $O(1)$ time.
    • Small Footprint: The struct itself is small, containing only a few fields to track the pointer, length, and shared state.
    • Interface-based: Bytes acts as an interface that uses dynamic dispatch (via a vtable) to handle different underlying storage implementations (e.g., static slices, reference-counted storage, or owned buffers).
    use bytes::Bytes;
    
    let mut mem = Bytes::from("Hello world");
    let a = mem.slice(0..5);
    
    assert_eq!(a, "Hello");
    
    let b = mem.split_to(6);
    
    assert_eq!(mem, "world");
    assert_eq!(b, "Hello ");
  4. What is BytesMut and how to use it

    master

    A BytesMut is a unique reference to a contiguous slice of memory that allows for efficient, mutable byte manipulation. It represents a unique view into a potentially shared memory region, providing a guarantee that no other BytesMut handle for the same underlying buffer overlaps with its slice. This uniqueness allows for mutation without a write lock.

    Key characteristics:

    • Growth: It implicitly grows its buffer via the BufMut trait, but explicit reservation is more efficient.
    • Immutability: You can convert a BytesMut into an immutable Bytes object using .freeze(), which is a zero-cost operation used to share data across threads.
    • Efficiency: Operations like split_off, split, and split_to are $O(1)$ as they only adjust indices and increment reference counts.
    use bytes::{BytesMut, BufMut};
    
    let mut buf = BytesMut::with_capacity(64);
    
    buf.put_u8(b'h');
    buf.put_u8(b'e');
    buf.put(&b"llo"[..]);
    
    assert_eq!(&buf[..], b"hello");
    
    // Freeze the buffer so that it can be shared
    let a = buf.freeze();
    
    // This does not allocate, instead `b` points to the same memory.
    let b = a.clone();
    
    assert_eq!(&a[..], b"hello");
    assert_eq!(&b[..], b"hello");
  5. How `Chain` works to concatenate buffers

    master

    A Chain is an adapter that sequences two underlying buffers, providing a single, continuous view across both. It can wrap either immutable buffers (Buf) or mutable buffers (BufMut).

    Instead of manually managing multiple buffers, you can use Chain to treat them as one. This is most commonly achieved by calling the Buf::chain method on an existing buffer.

    Chain implements the Buf and BufMut traits, meaning it behaves like a single buffer for operations like remaining(), advance(), and copy_to_bytes().

    use bytes::{Bytes, Buf};
    
    let mut buf = (&b"hello "[..])
        .chain(&b"world"[..]);
    
    let full: Bytes = buf.copy_to_bytes(11);
    assert_eq!(full[..], b"hello world"[..]);
  6. How `Bytes` and `BytesMut` work together for zero-copy

    master

    The bytes crate provides Bytes for efficient, immutable, and shareable byte storage, and BytesMut for efficient, mutable buffer management.

    Bytes facilitates zero-copy networking by allowing multiple Bytes handles to point to the same underlying memory via reference counting. A common pattern is to use BytesMut to write data into a buffer and then use .split() to create new Bytes handles that share the same underlying allocation. This avoids expensive reallocations and data copying.

    When you call .split() on a BytesMut, the resulting handle maintains its own indices into the shared buffer, allowing you to work with different views of the same memory.

    use bytes::{BytesMut, BufMut};
    
    let mut buf = BytesMut::with_capacity(1024);
    buf.put(&b"hello world"[..]);
    buf.put_u16(1234);
    
    // 'a' becomes a handle to the first part of the buffer
    let a = buf.split();
    assert_eq!(a, b"hello world\x04\xD2"[..]);
    
    // 'buf' now contains the remaining capacity
    // 'b' will be the next split
    buf.put(&b"goodbye world"[..]);
    let b = buf.split();
    assert_eq!(b, b"goodbye world"[..]);
    
    assert_eq!(buf.capacity(), 998);
  7. Read from a `Buf` using `Reader`

    master

    The Reader<B> struct is an adapter that implements std::io::Read and std::io::BufRead for any type B that implements the Buf trait. This allows you to use bytes buffers with standard library functions that expect types implementing io::Read (like io::copy).

    Typically, you should create a Reader by calling the .reader() method on a Buf instance rather than using Reader::new() directly.

    use bytes::Buf;
    use std::io;
    
    let mut buf = b"hello world".reader();
    let mut dst = vec![];
    
    // Use standard IO functions with the reader
    io::copy(&mut buf, &mut dst).unwrap();
  8. Limit bytes read using `Take`

    master

    The Take<T> struct is a Buf adapter that limits the number of bytes that can be read from an underlying buffer T. It is most commonly created by calling the .take(limit) method on an existing Buf implementation.

    When using Take, the remaining() method returns the minimum of the underlying buffer's remaining bytes and the specified limit. Advancing the buffer or copying bytes from it will decrease the remaining limit.

    use bytes::{Buf, BufMut};
    
    let mut buf = b"hello world".take(2);
    let mut dst = vec![];
    
    dst.put(&mut buf);
    assert_eq!(*dst, b"he"[..]);
  9. Use `Buf` and `BufMut` for infallible buffer access

    master

    The Buf and BufMut traits provide read and write access to byte buffers. Unlike std::io::Read and std::io::Write, which are used for I/O operations that can fail due to system calls, Buf and BufMut operations are infallible.

    Key characteristics:

    • Cursors: Both traits maintain cursors that track the current position in the buffer. Reading or writing automatically advances these cursors.
    • Memory Layout: The underlying storage does not need to be contiguous (e.g., a rope structure), though Bytes specifically guarantees contiguous memory.
    • Purpose: Use Buf/BufMut when you are manipulating data already in memory, rather than performing I/O against a file or socket.
  10. Enable Serde support for bytes

    master

    Serde support is optional and disabled by default. To enable serialization and deserialization support, enable the serde feature in your Cargo.toml.

    [dependencies]
    bytes = { version = "1", features = ["serde"]

    Note that the MSRV when the serde feature is enabled depends on the MSRV of serde.