Stream Concepts Overview

This section introduces Capy’s stream concepts—the abstractions that enable data to flow through your programs.

Prerequisites

  • Completed Buffer Sequences

  • Understanding of buffer sequences and the DynamicBuffer concept

Six Concepts for Data Flow

Capy defines six concepts for I/O operations, organized in three pairs:

Concept Direction Description

ReadStream

Read

Partial reads—returns whatever is available

WriteStream

Write

Partial writes—writes as much as possible

ReadSource

Read

Complete reads—fills buffer or signals EOF

WriteSink

Write

Complete writes with explicit EOF signaling

BufferSource

Read

Callee-owns-buffers read pattern

BufferSink

Write

Callee-owns-buffers write pattern

Streams vs Sources/Sinks

Streams: Partial I/O

Stream operations transfer some data and return. They do not guarantee a specific amount:

// ReadStream: may return fewer bytes than buffer can hold
auto [ec, n] = co_await stream.read_some(buffer);
// n might be 1, might be 1000, might be buffer_size(buffer)

// WriteStream: may write fewer bytes than provided
auto [ec, n] = co_await stream.write_some(buffers);
// n might be less than buffer_size(buffers)

This matches raw OS behavior—syscalls return when data is available, not when buffers are full.

Sources/Sinks: Complete I/O

Source/sink operations complete fully or signal completion:

// ReadSource: fills buffer completely, or returns EOF/error with partial
auto [ec, n] = co_await source.read(buffer);
// n == buffer_size(buffer), or ec indicates why not

// WriteSink: writes all data, with explicit EOF
co_await sink.write(buffers, true);  // true = EOF after this

These are higher-level abstractions built on streams.

Buffer Sources/Sinks: Callee-Owns-Buffers

The third pair inverts buffer ownership:

  • With streams/sources/sinks, the caller provides buffers

  • With buffer sources/sinks, the callee provides buffers

// BufferSource: callee provides read-only buffers
const_buffer bufs[8];
auto [ec, count] = co_await source.pull(bufs, 8);
// bufs[0..count-1] now point to source's internal data

// BufferSink: callee provides writable buffers
mutable_buffer bufs[8];
std::size_t count = sink.prepare(bufs, 8);
// Write into bufs[0..count-1], then commit
co_await sink.commit(bytes_written);

This pattern enables zero-copy I/O—data never moves through intermediate buffers.

Type-Erasing Wrappers

Each concept has a corresponding type-erasing wrapper:

Concept Wrapper

ReadStream

any_read_stream

WriteStream

any_write_stream

(Both)

any_stream

ReadSource

any_read_source

WriteSink

any_write_sink

BufferSource

any_buffer_source

BufferSink

any_buffer_sink

These wrappers enable:

  • APIs independent of concrete transport

  • Compilation firewalls (fast incremental builds)

  • Runtime polymorphism without virtual inheritance in user code

Choosing the Right Abstraction

Use Streams When:

  • You need raw, unbuffered I/O

  • You’re implementing a protocol that processes data incrementally

  • Performance is critical and you want minimal abstraction

Use Sources/Sinks When:

  • You need complete data units (messages, records, frames)

  • EOF signaling is part of your protocol

  • You’re composing transformations (compression, encryption)

Use Buffer Sources/Sinks When:

  • Zero-copy is essential

  • The source/sink owns the memory (memory-mapped files, hardware buffers)

  • You’re implementing a processing pipeline

The Value Proposition

Type-erased wrappers let you write transport-agnostic code:

// This function works with any stream implementation
task<> echo(any_stream& stream)
{
    char buf[1024];
    for (;;)
    {
        auto [ec, n] = co_await stream.read_some(mutable_buffer(buf));
        if (ec.failed())
            co_return;

        auto [wec, wn] = co_await write(stream, const_buffer(buf, n));
        if (wec.failed())
            co_return;
    }
}

The caller decides the concrete implementation:

// Works with Corosio TCP sockets
any_stream s1{tcp_socket};
echo(s1);

// Works with TLS streams
any_stream s2{tls_stream};
echo(s2);

// Works with test mocks
any_stream s3{test::stream{}};
echo(s3);

Same code, different transports—compile once, link anywhere.

Continue to Streams (Partial I/O) to learn the ReadStream and WriteStream concepts in detail.