Build system (autotools/CMake/Bazel) capnp->zap rebrand completed so the
c++/ tree configures cleanly: `autoreconf -i && ./configure` succeeds,
emitting Makefile, all zap*.pc / kj*.pc and the Zap CMake config. configure.ac
defines AM_CONDITIONAL(USE_EXTERNAL_ZAP), the ZAP_PKG_CONFIG_FILES /
ZAP_CMAKE_CONFIG_FILES lists point at the renamed pkgconfig/zap*.pc.in and
cmake/Zap*.cmake.in, and Makefile.am's `if USE_EXTERNAL_ZAP` matches. Bazel
loads @zap-cpp//src/zap:cc_zap_library.bzl. Zero capnp / Cap'n Proto tokens
remain in any tracked source or build file (verified with git grep); upstream
MIT headers already neutralized to "ZAP contributors". quick-test.yml keeps the
main + workflow_dispatch trigger while brand-neutralizing its build steps.
Whitespace-significant schema desugar (compiler/desugar.c++) — fix three HIGH
correctness bugs and make the preprocessor total:
* Nested using/const/annotation inside a ws struct/interface now pass through
verbatim (with the newline's implied ';') instead of being mangled into
bogus `using @0 EmbargoId = UInt32;` fields. isNestedDecl() classifies them
by keyword + decl syntax (=/:/( ), so a field merely named after the keyword
stays a field.
* Ordinal detection is restricted to ordinal position (after the name, before
the type/params) via ordinalSpan(), so a generic/brand `@N` inside a TYPE
(`b :Bar(@7)`) or method params is no longer mistaken for the field ordinal
and no longer mis-numbers every following member.
* Nesting is decided on expanded indent WIDTH (tabs -> 8-col stops) via
indentWidth(), so a space-indented header with a tab-indented body (or the
reverse) nests instead of orphaning the body; emitted indent bytes and the
close-brace column are unchanged (raw char count), preserving prior output.
* Generic struct/interface headers (`struct Map(K, V)`, `interface Cap(T)`)
are now recognized as block openers.
* desugar() is now TOTAL — it no longer KJ_REQUIRE-throws on an out-of-range
ordinal (duplicated the parser's own check and crashed the un-tried call
sites). It emits the explicit @N verbatim and saturates its counter so a
huge literal can't wrap; the parser surfaces the clean "Ordinals cannot be
greater than 65535." diagnostic at the right file:line:col. Removed the now
redundant validateAutoOrdinal and the dead kj/debug.h include. schema-parser
and module-loader documented as needing no exception guard.
desugar-test.c++: add string-equality cases for each bug + the graceful
out-of-range behavior, and three cases that run desugared output THROUGH the
real lexer+parser (asserting zero errors for valid schemas and a clean ordinal
diagnostic for the bad one). Brace back-compat proven byte-identical over the
18-file corpus; sample.ws.zap still desugars to sample.brace.zap exactly.
4.3 KiB
layout, title, author
| layout | title | author |
|---|---|---|
| post | Zap v0.4: Time Traveling RPC | kentonv |
Well, Hofstadter kicked in and this release took way too long. But, after three long months, I'm happy to announce:
Time-Traveling RPC (Promise Pipelining)
v0.4 finally introduces the long-promised [RPC system]({{ site.baseurl }}rpc.html). Traditionally, RPC is plagued by the fact that networks have latency, and pretending that latency doesn't exist by hiding it behind what looks like a normal function call only makes the problem worse. Zap has a simple solution to this problem: send call results back in time, so they arrive at the client at the point in time when the call was originally made!
Curious how Zap bypasses the laws of physics? [Check out the docs!]({{ site.baseurl }}rpc.html)
UPDATE: There has been some confusion about what I'm claiming. I am NOT saying that using promises alone (i.e. being asynchronous) constitutes "time travel". Zap implements a technique called Promise Pipelining which allows a new request to be formed based on the content of a previous result (in part or in whole) before that previous result is returned. Notice in the diagram that the result of foo() is being passed to bar(). Please [see the docs]({{ site.baseurl }}rpc.html) or check out the calculator example for more.
Promises in C++
UPDATE: More confusion. This section is not about pipelining ("time travel"). This section is just talking about implementing a promise API in C++. Pipelining is another feature on top of that. Please [see the RPC page]({{ site.baseurl }}rpc.html) if you want to know more about pipelining.
If you do a lot of serious JavaScript programming, you've probably heard of Promises/A+ and similar proposals. Zap RPC introduces a similar construct in C++. In fact, the API is nearly identical, and its semantics are nearly identical. Compare with Domenic Denicola's JavaScript example:
{% highlight c++ %} // C++ version of Domenic's JavaScript promises example. getTweetsFor("domenic") // returns a promise .then([](vector tweets) { auto shortUrls = parseTweetsForUrls(tweets); auto mostRecentShortUrl = shortUrls[0]; // expandUrlUsingTwitterApi returns a promise return expandUrlUsingTwitterApi(mostRecentShortUrl); }) .then(httpGet) // promise-returning function .then( [](string responseBody) { cout << "Most recent link text:" << responseBody << endl; }, [](kj::Exception&& error) { cerr << "Error with the twitterverse:" << error << endl; } ); {% endhighlight %}
This is C++, but it is no more lines -- nor otherwise more complex -- than the equivalent JavaScript. We're doing several I/O operations, we're doing them asynchronously, and we don't have a huge unreadable mess of callback functions. Promises are based on event loop concurrency, which means you can perform concurrent operations with shared state without worrying about mutex locking -- i.e., the JavaScript model. (Of course, if you really want threads, you can run multiple event loops in multiple threads and make inter-thread RPC calls between them.)
[More on C++ promises.]({{ site.baseurl }}cxxrpc.html#kj_concurrency_framework)
Python too
Jason has been diligently keeping his Python bindings up to date, so you can already use RPC there as well. The Python interactive interpreter makes a great debugging tool for calling C++ servers.
Up Next
Zap is far from done, but working on it in a bubble will not produce ideal results. Starting after the holidays, I will be refocusing some of my time into an adjacent project which will be a heavy user of Zap. I hope this experience will help me discover first hand the pain points in the current interface and keep development going in the right direction.
This does, however, mean that core Zap development will slow somewhat (unless contributors pick up the slack! ;) ). I am extremely excited about this next project, though, and I think you will be too. Stay tuned!