rest_rpc: a C++20 RPC layer where the handler signature is the contract
modern C++(C++20), simple, easy to use rpc framework
At a glance
- What is it?
- rest_rpc is a header only C++20 RPC library built on standalone asio, where a server registers a function and a client calls it by name, with struct_pack serialization, publish and subscribe on the same connection, and a zero-copy path for single basic type arguments. Its README claims the fastest RPC library in the world while publishing no measured numbers to back that claim.
- Who is it for?
- rest_rpc fits a C++20 service that already speaks TCP and wants its RPC surface to be the same C++ functions its callers already have, and it fits code where publish and subscribe over one connection are the point. It does not fit teams that need generated stubs or a versioned schema registry, because the repository holds no interface definition files.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 45 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Star history
- qicosmos/rest_rpc2,015 ★ · +1
| Repository | 2026-10-15 | 2026-10-23 |
|---|---|---|
| qicosmos/rest_rpc | 2k | 2k |
1 Stars in the last 8 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The same C++ signature is the entire client and server contract
There is no interface definition file anywhere in rest_rpc. A server registers a free function by naming it as a template argument, and a client invokes the very same name, so the compiler, not a code generator, is what keeps both sides in agreement. The handler takes the connection object as its first parameter and then the remote arguments, and its return value becomes the reply:
int add(rpc_conn conn, int a, int b) { return a + b; }
int main(){
rpc_server server("127.0.0.1:9004", std::thread::hardware_concurrency());
server.register_handler<add>();
server.start();
}The worker count is the hardware concurrency of the machine, and the endpoint in the example is port 9004 on the loopback address. On the client side the call is a coroutine, and the result arrives as a pair holding an error code and the value:
auto r = co_await client.call<add>(1, 2);
if(r.ec == rpc_errc::ok) {
REST_LOG_INFO << "call result: " << r.value;
assert(r.value == 3);
}Structs travel the same way. A plain aggregate holding an id, a name and an age is taken by value, mutated on the server, and returned, with no macro or registration step for the type. The cost of that convenience is that nothing on either side records a schema, so a signature change breaks the link at runtime rather than at build time.
The documented compiler floor sits above the compilers in the status table
The prose asks for a compiler with complete C++20 support and names gcc12+, clang15+ and msvc2022. The status table at the top of the same page reports successful builds on Ubuntu 22.04 with clang 14.0.0 and gcc 11.2.0, on macOS Monterey 12 with AppleClang 14.0.0.14000029, and on Windows Server 2022 with MSVC 19.33.31630.0. Those two lists do not agree. A badge reporting green on gcc 11.2.0 or clang 14.0.0 certifies a build on a compiler below the stated minimum, so the table cannot be used as evidence that the requirement holds. Anyone on the default compiler shipped with Ubuntu 22.04 is in exactly that position. The repository also carries both a .travis.yml file and codecov.yml alongside .github/, so the test matrix has moved across systems and left the older entries in place. No minimum standard is stated for consuming projects beyond that compiler list, and no build options are documented.
Publish and subscribe ride on the connection the client already opened
Starting a server for pubsub uses the asynchronous entry point rather than the blocking one, then publishes a typed value to a named channel, here topic1:
void publish() {
rpc_server server("127.0.0.1:9004", 4);
server.async_start();The client subscribes with the type it expects to receive back, and the library deserializes into it:
auto [ec, result] = co_await client.subscribe<std::string>("topic1");
if (ec != rpc_errc::ok) {
REST_LOG_ERROR << "subscribe failed: " << make_error_code(ec).message();
break;
}Two details reward a close read. First, the earlier request and response example checks the error code returned by connecting and logs it, while the pubsub example discards the result of connecting to 127.0.0.1:9004 entirely and enters its loop, so a refused connection is only reported later through the subscribe result. Second, nothing in the README says how many subscribers a topic accepts, how a subscriber that falls behind is handled, or whether topics can be listed. One client and one topic are all that is demonstrated.
Single argument calls skip serialization entirely, and that shapes your design
rest_rpc treats simple calls as a special case. When the argument is a std::string_view, the bytes go to the socket with no copy and no serialization step, and a handler returning std::string_view hands the client the received socket data untouched:
std::string_view echo(std::string_view str) {
return str;
}The stated rule is wider than the view case: a function whose parameters are a single basic type, string or numeric, is not serialized at all, while multiple arguments or a struct do go through the codec. That is why the add handler with three parameters is a serialized call while echo is not. The payoff is throughput on bulk transfers of several gigabytes. The price is that such a call carries no type tag, no field names and no version marker, so a caller and a server that disagree about whether a number is a string will not be caught by anything, and you cannot evolve a single argument message without breaking readers.
Serialization is replaced through argument dependent lookup, not a template parameter
The default codec is struct_pack from yalantinglibs. Replacing it does not mean passing a policy type: you define two functions, serialize and deserialize, in a namespace that argument dependent lookup can reach, and both take a rest_adl_tag. The example wires MessagePack into those two slots:
namespace user_codec {
// adl lookup in user_codec namespace
template <typename... Args>
std::string serialize(rest_adl_tag, Args &&...args) {
msgpack::sbuffer buffer(2 * 1024);
if constexpr (sizeof...(Args) > 1) {
msgpack::pack(buffer, std::forward_as_tuple(std::forward<Args>(args)...));
} else {
msgpack::pack(buffer, std::forward<Args>(args)...);
}Note the variable the deserialize half keeps. The sample holds a static unpacked object inside the function template, so for a given type every concurrent call touches the same instance, while the server runs several worker threads and each connection is served by coroutines. Copy that shape into a real project and two in-flight requests of the same type can overwrite each other's decode state. That is an artifact of the sample, not of the library, but the sample is what a reader copies first.
The speed claim points at a benchmark file instead of publishing numbers
The README states that rest_rpc is currently the fastest RPC library, says the comparison was made against grpc and brpc, and asserts the result exceeds grpc by a wide margin. The only evidence offered is a path: tests/bench.cpp. No measured values, no machine description, no payload size, no concurrency level and no protocol configuration appear in the text. A reader cannot reproduce or even approximate the ranking from what is written, and codecov.yml at the top level shows the project tracks test coverage rather than publishing benchmark results. Keep in mind the codec is swappable, so a number measured on the default struct_pack path says little about a deployment that swaps in MessagePack. Treat the claim as a pointer to a file to read, not as a result.
Adopting it means working from the repository build files, since no install step exists
There is no install command and no CMake snippet in the documentation. What the repository provides is the full source tree: a top level CMakeLists.txt, a cmake/ directory, headers under include/, examples/, tests/ and thirdparty/, alongside .github/, .travis.yml, codecov.yml, .gitignore and README.md. The only named example files are examples/CMakeLists.txt, examples/server.cpp and examples/client.cpp, which is where a first run starts. The single stated dependency is the header only standalone asio, pinned at tag asio-1-36-0, and because the library is header only there is nothing to link beyond your own build. An MIT licence sits at the top level, which for most teams means no restriction on linking into a closed source service, though that reading is yours to make rather than the project's. Discussion happens at purecpp.cn and its chatroom page rather than on the repository itself.
Editorial conclusion
rest_rpc fits a C++20 service that already speaks TCP and wants its RPC surface to be the same C++ functions its callers already have, and it fits code where publish and subscribe over one connection are the point. It does not fit teams that need generated stubs or a versioned schema registry, because the repository holds no interface definition files. The last push was on 2026-09-08 and the v0.21 tag dates from 2026-01-09. Verify your compiler clears gcc12+ or clang15+, then read tests/bench.cpp before repeating the speed claim.
Frequently asked questions
What compiler does rest_rpc require?
The documentation asks for a compiler with full C++20 support: gcc12+, clang15+ or msvc2022. Its status table reports builds on Ubuntu 22.04 (gcc 11.2.0, clang 14.0.0), macOS Monterey 12 (AppleClang 14.0.0.14000029) and Windows Server 2022 (MSVC 19.33.31630.0), so the badge compilers sit below the stated floor.
How do I install rest_rpc?
The README gives no install command and no CMake snippet. The code lives in the repository with headers under include/ and CMakeLists.txt, cmake/, examples/, tests/ and thirdparty/ at the top level, and it depends on the header only standalone asio at tag asio-1-36-0, so nothing has to be built or linked beforehand.
Does rest_rpc serialize every argument I pass?
No. A function taking a single basic type argument, or a std::string_view, is sent without serialization, and a std::string_view return value comes back without deserialization. Serialization is applied only when a call has multiple arguments or a struct.
Can rest_rpc use MessagePack instead of the default struct_pack?
Yes. Define serialize and deserialize in a namespace reachable by argument dependent lookup, both taking a rest_adl_tag, as the user_codec sample does with msgpack::pack and msgpack::unpack, and the library uses those two functions instead of the default codec.
How does rest_rpc compare with gRPC in speed?
The README states rest_rpc is the fastest RPC library, that it was compared with grpc and brpc, and that it exceeds grpc by a wide margin, then points at tests/bench.cpp. It publishes no measured values, machine description or protocol configuration for that comparison.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/qicosmos-rest-rpc)Star history chart for your README
[](https://hysenlabs.com/projects/qicosmos-rest-rpc)Was this useful?