Type-Erased Echo

Echo server demonstrating the compilation firewall pattern.

What You Will Learn

  • Using any_stream for transport-independent code

  • Physical isolation through separate compilation

  • Build time benefits of type erasure

Prerequisites

Source Code

echo.hpp

#ifndef ECHO_HPP
#define ECHO_HPP

#include <boost/capy/io/any_stream.hpp>
#include <boost/capy/task.hpp>

namespace myapp {

// Type-erased interface: no template dependencies
boost::capy::task<> echo_session(boost::capy::any_stream& stream);

} // namespace myapp

#endif

echo.cpp

#include "echo.hpp"
#include <boost/capy/read.hpp>
#include <boost/capy/write.hpp>

namespace myapp {

using namespace boost::capy;

task<> echo_session(any_stream& stream)
{
    char buffer[1024];

    for (;;)
    {
        // Read some data
        auto [ec, n] = co_await stream.read_some(mutable_buffer(buffer));

        if (ec == cond::eof)
            co_return;  // Client closed connection

        if (ec.failed())
            throw std::system_error(ec);

        // Echo it back
        auto [wec, wn] = co_await write(stream, const_buffer(buffer, n));

        if (wec.failed())
            throw std::system_error(wec);
    }
}

} // namespace myapp

main.cpp

#include "echo.hpp"
#include <boost/capy.hpp>
#include <boost/capy/test/stream.hpp>
#include <boost/capy/test/run_blocking.hpp>
#include <iostream>

using namespace boost::capy;

void test_with_mock()
{
    test::stream mock;
    mock.provide("Hello, ");
    mock.provide("World!\n");
    mock.provide_eof();

    any_stream stream{mock};
    test::run_blocking(myapp::echo_session(stream));

    std::cout << "Echo output: " << mock.output() << "\n";
}

int main()
{
    test_with_mock();

    // With real sockets (using Corosio):
    // tcp::socket socket;
    // ... accept connection ...
    // any_stream stream{socket};
    // co_await myapp::echo_session(stream);

    return 0;
}

Build

add_library(echo_lib echo.cpp)
target_link_libraries(echo_lib PUBLIC capy)

add_executable(echo_demo main.cpp)
target_link_libraries(echo_demo PRIVATE echo_lib)

Walkthrough

The Interface

// echo.hpp
task<> echo_session(any_stream& stream);

The header declares only the signature. It includes any_stream and task, but no concrete transport types.

Clients of this header:

  • Can call echo_session with any stream

  • Do not depend on implementation details

  • Do not recompile when implementation changes

The Implementation

// echo.cpp
task<> echo_session(any_stream& stream)
{
    // Full implementation here
}

The implementation:

  • Lives in a separate .cpp file

  • Compiles once

  • Can include any headers it needs internally

Build Isolation

When you change echo.cpp:

  • Only echo.cpp recompiles

  • main.cpp and other clients do not recompile

  • Link step updates the binary

This scales: in large projects, changes to implementation files don’t cascade through dependencies.

Output

Echo output: Hello, World!

Exercises

  1. Add logging to echo_session and observe that clients don’t recompile

  2. Create a second implementation file with different behavior (e.g., uppercase echo)

  3. Measure compile times with and without type erasure in a larger project

Next Steps