Stream Concepts Overview

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

Prerequisites

  • Understanding of buffer sequences

Three Concepts for Data Flow

Capy defines three concepts for I/O operations:

Concept Direction Description

ReadStream

Read

Partial reads—returns whatever is available

WriteStream

Write

Partial writes—writes as much as possible

Stream

Read + Write

A connected pair—satisfies both ReadStream and WriteStream

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.

Type-Erasing Wrappers

Each concept has a corresponding type-erasing wrapper:

Concept Wrapper

ReadStream

any_read_stream

WriteStream

any_write_stream

(Both)

any_stream

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

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(make_buffer(buf));

        auto [wec, wn] = co_await write(stream, const_buffer(buf, n));

        if (ec)
            co_return;

        if (wec)
            co_return;
    }
}

The caller decides the concrete implementation:

// Owns a moved-in TCP socket (the lvalue form takes ownership by value)
any_stream s1{tcp_socket};
echo(s1);

// Wraps a TLS stream by pointer (reference semantics, must outlive s2)
any_stream s2{&tls_stream};
echo(s2);

// Owns a temporary test mock
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.