How to Use absl::Cord with Async I/O Frameworks for Zero-Copy Operations

Use absl::CordBuffer to allocate reference-counted buffers that async I/O frameworks fill directly, then append them to an absl::Cord with Append(std::move(buffer)) to eliminate data copying, or wrap existing memory with absl::MakeCordFromExternal for zero-copy integration with external data sources.

absl::Cord in the abseil-cpp repository provides a chunk-based string container that enables true zero-copy data flows across asynchronous I/O pipelines. Unlike traditional std::string containers that require contiguous memory, absl::Cord stores data in a tree of reference-counted buffers, allowing you to hand memory directly to async frameworks like Boost.Asio or libuv without copying underlying bytes. This article demonstrates how to use absl::Cord with async I/O frameworks for zero-copy operations using the actual implementation from the Abseil source code.

Understanding CordBuffer and Cord Architecture

The zero-copy pattern relies on two core components: the move-only CordBuffer class for raw memory management and the reference-counted Cord container that owns the data.

CordBuffer Allocation and Management

absl::CordBuffer is a move-only object defined in absl/strings/cord_buffer.h that manages raw memory regions. When you call absl::CordBuffer::CreateWithDefaultLimit(size) or CreateWithCustomLimit(size), the library allocates a buffer capable of holding at least the requested bytes while respecting internal block size limits.

To prepare the buffer for async I/O operations:

  1. Call buffer.available_up_to(requested_bytes) to obtain an absl::Span<char> that the framework can write into directly.
  2. After the async operation completes, call buffer.IncreaseLengthBy(written) or buffer.SetLength(total) to update the actual payload size.

Internal Cord Representation

According to the source code in absl/strings/internal/cord_rep_btree.h and absl/strings/internal/cord_rep_flat.h, absl::Cord stores data as a tree of CordRep nodes. When you call cord.Append(std::move(buffer)) as implemented in absl/strings/cord.h, the Cord inserts the buffer's internal CordRepFlat node as a new leaf in the tree. This operation transfers ownership without copying the underlying bytes—the Cord simply stores a pointer to the existing memory location.

Zero-Copy Read Patterns with Boost.Asio

When reading data asynchronously, allocate a CordBuffer, pass its writable span to the I/O framework, then append the filled buffer to your Cord.

The following example demonstrates reading from a TCP socket using Boost.Asio:

#include "absl/strings/cord.h"
#include "absl/strings/cord_buffer.h"
#include <boost/asio.hpp>

absl::Cord ReadAsync(boost::asio::ip::tcp::socket& socket,
                    std::size_t total_bytes) {
  absl::Cord result;
  std::size_t remaining = total_bytes;

  while (remaining > 0) {
    // Allocate a buffer that will hold at most 4 KiB (default limit)
    absl::CordBuffer buf = absl::CordBuffer::CreateWithDefaultLimit(remaining);
    // Provide a span the async read operation can fill.
    absl::Span<char> writable = buf.available_up_to(remaining);

    // Boost.Asio async read (lambda callback for simplicity)
    std::size_t n = boost::asio::read(socket,
                                      boost::asio::buffer(writable.data(),
                                                          writable.size()));
    // Tell the buffer how many bytes were really written.
    buf.IncreaseLengthBy(n);
    // Append the filled buffer to the Cord – zero-copy!
    result.Append(std::move(buf));

    remaining -= n;
  }
  return result;   // `result` now owns all data without copies.
}

Key functions demonstrated include CordBuffer::CreateWithDefaultLimit, available_up_to, IncreaseLengthBy, and Cord::Append(CordBuffer&&).

Zero-Copy Write Patterns with libuv

For writing data without copying, iterate over the Cord's chunks using Cord::Chunks() and pass each chunk directly to the async writer. Each chunk returned is an absl::string_view referencing contiguous internal memory.

This example shows writing a Cord to a libuv stream:

#include "absl/strings/cord.h"
#include "absl/strings/cord_buffer.h"
#include <uv.h>

// Helper that writes a Cord to a libuv stream in chunks.
void WriteCordAsync(uv_stream_t* stream, const absl::Cord& cord) {
  // Iterate over each chunk; each chunk is already a contiguous buffer.
  for (absl::string_view chunk : cord.Chunks()) {
    // Allocate a libuv write request.
    uv_write_t* req = new uv_write_t;
    uv_buf_t uvbuf = uv_buf_init(const_cast<char*>(chunk.data()),
                                 static_cast<unsigned int>(chunk.size()));
    // libuv takes ownership of the buffer only until the callback returns.
    // Because `chunk` lives inside the Cord, we keep the Cord alive.
    uv_write(req, stream, &uvbuf, 1,
             [](uv_write_t* req, int status) {
               // Clean up the request; Cord body remains valid.
               delete req;
             });
  }
}

Note that Cord::Chunks() returns an iterator of absl::string_view objects that directly reference each internal chunk. The network stack streams each chunk as-is without serialization.

Wrapping External Memory with MakeCordFromExternal

When you already own a memory region—such as an mmap'ed file or network-received buffer—you can wrap it in a Cord without copying using absl::MakeCordFromExternal. This function, implemented in absl/strings/cord.h (lines 1166-1172), accepts a custom releaser callable that executes when the last reference to the data is dropped.

The following example demonstrates wrapping an mmap'ed file:

#include "absl/strings/cord.h"
#include <sys/mman.h>
#include <unistd.h>

absl::Cord MakeCordFromMmap(const char* path) {
  // Open and mmap the file (error handling omitted for brevity).
  int fd = open(path, O_RDONLY);
  off_t size = lseek(fd, 0, SEEK_END);
  void* addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
  close(fd);

  // Create a Cord that adopts the mmap'ed region.
  // The releaser will `munmap` the memory when the Cord is destroyed.
  auto releaser = [size](absl::string_view){ munmap(addr, size); };
  return absl::MakeCordFromExternal(absl::string_view(static_cast<char*>(addr), size),
                                    std::move(releaser));
}

The releaser lambda ensures that munmap is called only after all references to the Cord are destroyed, preserving zero-copy semantics across asynchronous pipelines while managing resource lifetimes safely.

Summary

  • Allocate zero-copy buffers using absl::CordBuffer::CreateWithDefaultLimit or CreateWithCustomLimit from absl/strings/cord_buffer.h.
  • Transfer ownership to a Cord via Append(std::move(buffer)) without copying data; the Cord stores the buffer as a CordRepFlat leaf node.
  • Read from Cord chunks using Cord::Chunks() to obtain absl::string_view references to contiguous internal data.
  • Wrap external memory using absl::MakeCordFromExternal with a custom releaser for zero-copy integration with mmap or custom allocators.
  • Convert to contiguous views when necessary using Cord::TryFlat() (zero-copy when possible) or Cord::Flatten() (copying only when required).

Frequently Asked Questions

What is the difference between CordBuffer and std::string for async I/O?

absl::CordBuffer is a move-only object that owns a reference-counted memory region and can be transferred into an absl::Cord without copying, whereas std::string always requires contiguous memory and copying to append or transfer ownership. The CordBuffer API allows direct access to writable spans via available_up_to(), enabling async frameworks to write directly into the buffer that will become part of the Cord tree.

When should I use MakeCordFromExternal versus CordBuffer?

Use absl::MakeCordFromExternal when you already own a memory region (such as an mmap'ed file or a buffer received from a kernel-bypass network stack) and want to attach it to a Cord without copying. Use absl::CordBuffer when you need the library to allocate memory for incoming data that will be filled by an async read operation. Both approaches preserve zero-copy semantics but differ in who owns the initial memory allocation.

How do I ensure thread safety when sharing Cord across async callbacks?

absl::Cord uses reference counting internally (as seen in absl/strings/internal/cord_rep_btree.h), making it thread-safe for read operations and cheap to copy. When you pass a Cord to another async stage or thread, pass it by value or const reference. The underlying data remains valid until the last reference is destroyed. However, CordBuffer is move-only and not thread-safe; it must be fully constructed in one thread before being moved into a Cord.

Can I convert a Cord back to a contiguous buffer if the downstream API requires it?

Yes. Call Cord::TryFlat() to obtain an absl::string_view without copying if the Cord happens to be stored as a single contiguous chunk. If the Cord contains multiple chunks, Cord::Flatten() will allocate a new contiguous buffer and copy the data. According to absl/strings/cord.h, TryFlat() returns a string_view only when the Cord is already flat, allowing you to optimize for the zero-copy case while having a fallback to Flatten() when necessary.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →