Stream Concepts

This page explains Capy’s stream concepts for generic I/O operations. Understanding the distinction between partial and complete operations is key to writing correct network code.

Code snippets assume using namespace boost::capy; is in effect.

Stream vs Source/Sink

Capy distinguishes between two levels of I/O abstraction:

Level Concepts Behavior

Partial

ReadStream, WriteStream

Single operation, may transfer less than requested

Complete

ReadSource, WriteSink

Loops internally, fills/drains buffers completely

The stream concepts (read_some/write_some) map closely to OS system calls. The source/sink concepts (read/write) build loops around partial operations.

ReadStream

A ReadStream provides partial read operations via read_some():

template<typename T>
concept ReadStream = requires(T& stream, MutableBufferSequence auto buffers)
{
    { stream.read_some(buffers) } -> IoAwaitable;
    // Returns io_result<std::size_t>
};

Behavior

read_some() reads at least one byte (unless EOF or error) but may read fewer bytes than the buffer can hold:

char buffer[1024];
auto [ec, n] = co_await stream.read_some(mutable_buffer(buffer, 1024));
// n might be 100, even though buffer holds 1024

This matches OS behavior—recv() returns whatever data is available.

Return Values

  • Success: !ec.failed() is true, n >= 1

  • EOF: ec == cond::eof, n == 0

  • Error: ec.failed() is true, n == 0

Example

task<std::string> read_available(ReadStream auto& stream)
{
    char buffer[4096];
    auto [ec, n] = co_await stream.read_some(make_buffer(buffer));
    if (ec.failed())
        co_return {};
    co_return std::string(buffer, n);
}

Reference: <boost/capy/concept/read_stream.hpp>

WriteStream

A WriteStream provides partial write operations via write_some():

template<typename T>
concept WriteStream = requires(T& stream, ConstBufferSequence auto buffers)
{
    { stream.write_some(buffers) } -> IoAwaitable;
    // Returns io_result<std::size_t>
};

Behavior

write_some() writes at least one byte (unless error) but may write fewer bytes than provided:

std::string data = /* 10000 bytes */;
auto [ec, n] = co_await stream.write_some(make_buffer(data));
// n might be 8192, even though data has 10000 bytes

This matches OS behavior—send() may accept only part of your data.

Return Values

  • Success: !ec.failed() is true, n >= 1

  • Error: ec.failed() is true, n == 0

Example

task<void> write_chunk(WriteStream auto& stream, std::string_view data)
{
    auto [ec, n] = co_await stream.write_some(make_buffer(data));
    if (ec.failed())
        co_return;
    // Only n bytes were written
}

Reference: <boost/capy/concept/write_stream.hpp>

ReadSource

A ReadSource provides complete read operations via read():

template<typename T>
concept ReadSource = requires(T& source, MutableBufferSequence auto buffers)
{
    { source.read(buffers) } -> IoAwaitable;
    // Returns io_result<std::size_t>
};

Behavior

read() fills the buffer completely before returning, looping as needed:

char header[16];
auto [ec, n] = co_await source.read(mutable_buffer(header, 16));
// If successful: n == 16 (always fills completely)

Return Values

  • Success: !ec.failed() is true, n == buffer_size(buffers)

  • EOF: ec == cond::eof, n is bytes read before EOF

  • Error: ec.failed() is true, n is bytes read before error

Example

task<std::vector<char>> read_exact(ReadSource auto& source, std::size_t count)
{
    std::vector<char> result(count);
    auto [ec, n] = co_await source.read(make_buffer(result));
    if (ec.failed())
        co_return {};
    co_return result;
}

Reference: <boost/capy/concept/read_source.hpp>

WriteSink

A WriteSink provides complete write operations with EOF signaling:

template<typename T>
concept WriteSink = requires(T& sink, ConstBufferSequence auto buffers, bool eof)
{
    { sink.write(buffers) } -> IoAwaitable;        // Write all bytes
    { sink.write(buffers, eof) } -> IoAwaitable;   // Write + optional EOF
    { sink.write_eof() } -> IoAwaitable;           // Signal end of data
};

Behavior

write() sends all bytes before returning:

std::string body = /* 10000 bytes */;
auto [ec, n] = co_await sink.write(make_buffer(body));
// If successful: n == 10000 (always writes completely)

The EOF variants signal that no more data will follow:

// Option 1: write then signal EOF
co_await sink.write(make_buffer(data));
co_await sink.write_eof();

// Option 2: combined call
co_await sink.write(make_buffer(data), true);

Return Values

  • Success: !ec.failed() is true, n == buffer_size(buffers)

  • Error: ec.failed() is true, n is bytes written before error

Reference: <boost/capy/concept/write_sink.hpp>

Composed Algorithms

Capy provides read() and write() free functions that build complete operations from partial ones.

read() Function

#include <boost/capy/read.hpp>

// Read into fixed buffers (loops read_some until full)
auto [ec, n] = co_await read(stream, buffers);

// Read into dynamic buffer (loops until EOF)
auto [ec, n] = co_await read(source, dynamic_buffer);

write() Function

#include <boost/capy/write.hpp>

// Write all data (loops write_some until empty)
auto [ec, n] = co_await write(stream, buffers);

Choosing the Right Level

Use stream concepts (read_some/write_some) when:

  • You want to process data as it arrives

  • You’re implementing a protocol with natural chunk boundaries

  • You need fine-grained control over I/O timing

Use source/sink concepts (read/write) when:

  • You need exactly N bytes before proceeding

  • You’re reading until EOF

  • You want simple "send this, receive that" semantics

Example: Echo Server

// Using partial operations for streaming
task<void> echo_streaming(ReadStream auto& in, WriteStream auto& out)
{
    char buffer[4096];
    for (;;)
    {
        auto [ec1, n] = co_await in.read_some(make_buffer(buffer));
        if (ec1 == cond::eof)
            break;
        if (ec1.failed())
            co_return;

        // Use write() to ensure all bytes are sent
        auto [ec2, _] = co_await write(out, make_buffer(buffer, n));
        if (ec2.failed())
            co_return;
    }
}

Summary

Concept Method Behavior

ReadStream

read_some(buffers)

Read at least one byte (partial)

WriteStream

write_some(buffers)

Write at least one byte (partial)

ReadSource

read(buffers)

Fill buffers completely or EOF

WriteSink

write(buffers), write_eof()

Write all bytes, signal EOF