|
| 1 | +# Folly Integration Example |
| 2 | + |
| 3 | +This example demonstrates how to use `cxx-async` to seamlessly integrate Rust async code with Facebook's [Folly](https://github.com/facebook/folly) C++ async framework. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +The example showcases bidirectional async interoperability between Rust and C++ using Folly coroutines and futures. It demonstrates various async patterns including: |
| 8 | + |
| 9 | +- Rust calling C++ async functions (using both Folly coroutines and futures) |
| 10 | +- C++ calling Rust async functions |
| 11 | +- Exception/error handling across language boundaries |
| 12 | +- Async streams (generators) support |
| 13 | +- Complex async patterns like ping-pong communication |
| 14 | + |
| 15 | +## Key Components |
| 16 | + |
| 17 | +### Rust Side (`src/main.rs`) |
| 18 | + |
| 19 | +- **Bridge Definitions**: Uses `#[cxx::bridge]` and `#[cxx_async::bridge]` to define FFI bindings for async types |
| 20 | +- **Async Functions**: Implements Rust async functions that can be called from C++ |
| 21 | +- **Test Suite**: Comprehensive tests covering various interop scenarios |
| 22 | + |
| 23 | +### C++ Side |
| 24 | + |
| 25 | +- **`include/folly_example.h`**: Header file defining the C++ async interface using `CXXASYNC_DEFINE_FUTURE` and `CXXASYNC_DEFINE_STREAM` macros |
| 26 | +- **`src/folly_example.cpp`**: Implementation using Folly coroutines (`folly::coro::Task`) and futures |
| 27 | + |
| 28 | +## Features Demonstrated |
| 29 | + |
| 30 | +### 1. Parallel Computation |
| 31 | + |
| 32 | +The example implements a parallel dot product calculation that: |
| 33 | +- Recursively splits the work when the array size exceeds a threshold |
| 34 | +- Spawns tasks on thread pools (both Rust and C++ sides) |
| 35 | +- Aggregates results asynchronously |
| 36 | + |
| 37 | +```cpp |
| 38 | +// C++ side using Folly coroutines |
| 39 | +static folly::coro::Task<double> do_dot_product_coro(const double a[], const double b[], size_t count) { |
| 40 | + if (count > EXAMPLE_SPLIT_LIMIT) { |
| 41 | + // Split and compute in parallel |
| 42 | + auto [first, second] = co_await folly::collectAll(taskA, taskB); |
| 43 | + co_return *first + *second; |
| 44 | + } |
| 45 | + // Base case: compute directly |
| 46 | +} |
| 47 | +``` |
| 48 | +
|
| 49 | +```rust |
| 50 | +// Rust side using async/await |
| 51 | +#[async_recursion] |
| 52 | +async fn dot_product(range: Range<usize>) -> f64 { |
| 53 | + if len > SPLIT_LIMIT { |
| 54 | + // Split and compute in parallel |
| 55 | + let (first, second) = join!(/* ... */); |
| 56 | + return first + second; |
| 57 | + } |
| 58 | + // Base case: compute directly |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +### 2. Exception Handling |
| 63 | + |
| 64 | +Custom exception handling across the FFI boundary: |
| 65 | + |
| 66 | +```cpp |
| 67 | +// C++ custom exception |
| 68 | +class MyException : public std::exception { |
| 69 | + const char* message() const noexcept; |
| 70 | +}; |
| 71 | + |
| 72 | +// Template specialization for exception handling |
| 73 | +template <typename T> |
| 74 | +struct TryCatch<T, Custom> { |
| 75 | + static void trycatch(Try&& func, Fail&& fail) noexcept { |
| 76 | + try { |
| 77 | + func(); |
| 78 | + } catch (const MyException& exception) { |
| 79 | + fail(exception.message()); |
| 80 | + } |
| 81 | + } |
| 82 | +}; |
| 83 | +``` |
| 84 | +
|
| 85 | +### 3. Async Streams |
| 86 | +
|
| 87 | +The example includes FizzBuzz implementations using async streams: |
| 88 | +
|
| 89 | +```cpp |
| 90 | +// C++ generator coroutine |
| 91 | +RustStreamString folly_fizzbuzz() { |
| 92 | + for (int i = 1; i <= 15; i++) { |
| 93 | + if (i % 15 == 0) { |
| 94 | + co_yield rust::String("FizzBuzz"); |
| 95 | + } else if (i % 5 == 0) { |
| 96 | + co_yield rust::String("Buzz"); |
| 97 | + } // ... |
| 98 | + } |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +### 4. Ping-Pong Pattern |
| 103 | + |
| 104 | +Demonstrates multiple async calls across language boundaries: |
| 105 | + |
| 106 | +```rust |
| 107 | +// Rust side |
| 108 | +fn rust_folly_ping_pong(i: i32) -> RustFutureString { |
| 109 | + RustFutureString::infallible(async move { |
| 110 | + format!("{}ping ", |
| 111 | + if i < 4 { |
| 112 | + ffi::folly_ping_pong(i + 1).await.unwrap() |
| 113 | + } else { |
| 114 | + "".to_owned() |
| 115 | + }) |
| 116 | + }) |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +## Building and Running |
| 121 | + |
| 122 | +### Prerequisites |
| 123 | + |
| 124 | +- Folly library installed (the build uses `find-folly` crate) |
| 125 | +- C++20 compiler support |
| 126 | +- Rust toolchain |
| 127 | + |
| 128 | +### Build |
| 129 | + |
| 130 | +```bash |
| 131 | +cargo build --example folly |
| 132 | +``` |
| 133 | + |
| 134 | +### Run Tests |
| 135 | + |
| 136 | +```bash |
| 137 | +cargo test --example folly |
| 138 | +``` |
| 139 | + |
| 140 | +### Run Example |
| 141 | + |
| 142 | +```bash |
| 143 | +cargo run --example folly |
| 144 | +``` |
| 145 | + |
| 146 | +## Test Coverage |
| 147 | + |
| 148 | +The example includes extensive tests for: |
| 149 | + |
| 150 | +- Synchronous calling patterns (Rust → C++, C++ → Rust) |
| 151 | +- Asynchronous execution on thread pools |
| 152 | +- Exception and error propagation |
| 153 | +- Void return types |
| 154 | +- Future dropping and cleanup |
| 155 | +- Stream operations with exceptions |
| 156 | +- Coroutine lifecycle management |
| 157 | + |
| 158 | +## Architecture Notes |
| 159 | + |
| 160 | +### Thread Pools |
| 161 | + |
| 162 | +- **C++ Side**: Uses `folly::CPUThreadPoolExecutor` with 8 threads |
| 163 | +- **Rust Side**: Uses `futures::executor::ThreadPool` |
| 164 | + |
| 165 | +### Memory Management |
| 166 | + |
| 167 | +The example demonstrates proper memory management: |
| 168 | +- Coroutines run to completion ensuring destructors are called |
| 169 | +- Futures can be safely dropped |
| 170 | +- Background tasks are properly managed through Folly's reaper |
| 171 | + |
| 172 | +### Type Mapping |
| 173 | + |
| 174 | +| Rust Type | C++ Type | Purpose | |
| 175 | +|-----------|----------|---------| |
| 176 | +| `RustFutureVoid` | `folly::coro::Task<void>` | Async operations without return value | |
| 177 | +| `RustFutureF64` | `folly::coro::Task<double>` | Async operations returning floating point | |
| 178 | +| `RustFutureString` | `folly::coro::Task<rust::String>` | Async operations returning strings | |
| 179 | +| `RustStreamString` | Generator coroutine | Async stream of strings | |
| 180 | + |
| 181 | +## Performance Considerations |
| 182 | + |
| 183 | +The example is designed to showcase real parallelism: |
| 184 | +- Work is distributed across multiple threads |
| 185 | +- Large arrays (16384 elements) ensure meaningful computation |
| 186 | +- Split threshold (32 elements) balances parallelism overhead |
| 187 | + |
| 188 | +## Further Reading |
| 189 | + |
| 190 | +- [Folly Documentation](https://github.com/facebook/folly/tree/main/folly/docs) |
| 191 | +- [cxx-async Documentation](https://github.com/pcwalton/cxx-async) |
| 192 | +- [CXX Documentation](https://cxx.rs/) |
0 commit comments