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

> Master zero-copy operations with absl::Cord and async I/O. Learn to use absl::CordBuffer and external memory integration for efficient data handling and boosted performance.

- Repository: [Abseil/abseil-cpp](https://github.com/abseil/abseil-cpp)
- Tags: how-to-guide
- Published: 2026-07-11

---

**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`](https://github.com/abseil/abseil-cpp/blob/main/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`](https://github.com/abseil/abseil-cpp/blob/main/absl/strings/internal/cord_rep_btree.h) and [`absl/strings/internal/cord_rep_flat.h`](https://github.com/abseil/abseil-cpp/blob/main/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`](https://github.com/abseil/abseil-cpp/blob/main/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:

```cpp
#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:

```cpp
#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`](https://github.com/abseil/abseil-cpp/blob/main/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:

```cpp
#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`](https://github.com/abseil/abseil-cpp/blob/main/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`](https://github.com/abseil/abseil-cpp/blob/main/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`](https://github.com/abseil/abseil-cpp/blob/main/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.