Rclcpp_async: Header-only C++20 async/await for ROS 2

Hi!

Inspired by icey (great library!), I put together a quick prototype of something similar – a header-only library that lets you write ROS 2 async code with C++20 coroutines.

GitHub: GitHub - otamachan/rclcpp_async

Motivation

I wanted something like Python’s asyncio for ROS 2 C++ – write asynchronous code that reads like sequential code, no callback nesting, no deadlocks on single-threaded executors. C++20 coroutines make this straightforward.

Example

Task<void> run(CoContext & ctx)
{
  auto client = ctx.node()->create_client<SetBool>("set_bool");
  auto wait = co_await ctx.wait_for_service(client);
  if (!wait.ok()) {
    co_return;
  }

  auto req = std::make_shared<SetBool::Request>();
  req->data = true;
  auto result = co_await ctx.send_request<SetBool>(client, req);
  if (result.ok()) {
    RCLCPP_INFO(ctx.node()->get_logger(), "Response: %s",
      result.value.value()->message.c_str());
  }
}

No special executor needed – just rclcpp::spin(node).

What it covers

  • Header-only, no dependencies beyond rclcpp
  • Service client/server, Action client/server
  • TopicStream / TimerStream (async iterators for subscriptions and timers)
  • Event / Mutex / Channel (coroutine-aware sync primitives)
  • Cancellation propagation

Requires ROS 2 Jazzy + GCC 13+ (C++20).

Still a prototype, but feedback is welcome!

6 Likes

Nice to list your inspiration!
I evaluated icey last week. How does this header file compare to the icey package?

Thanks for looking into it!

The main differences are:

rclcpp_async icey
Type Header-only Compiled library
Dependencies rclcpp only rclcpp + Boost (Hana) + FMT
Style Task<T> + async streams Promise<T, E> + reactive streams
Services Yes Yes
Actions Yes No
TF / Lifecycle No Yes

Both use co_await and work with the standard single-threaded executor. icey has a broader scope with TF, lifecycle, and message_filters integration. rclcpp_async is more minimal but covers actions and provides coroutine-aware sync primitives (Event, Mutex, Channel) and Python asyncio-style create_task + co_await for spawning and joining background tasks.

5 Likes