Today, we are thrilled to announce the release of CAF version 1.0. This is a major milestone for us and we are excited to share it with you.
CAF has been in beta for over a decade. During that time, we have revised the design multiple times, continuously added features and received valuable feedback from our users. We are very grateful for the comments, suggestions and bug reports we have received over the years. Be it on GitHub, in chat, via email or in private conversations, your feedback has been invaluable to us.
For this milestone release, we have asked ourselves how CAF would look like if we started from scratch today and how we want to evolve CAF over the next decade. Of course, we did not throw away everything we have built so far. However, we have thought of all the lessons we have learned, considered the community feedback we have received and discussed our experience of teaching CAF to new users.
Our goal for CAF version 1.0 is to provide a solid foundation for the next decade. We want to make CAF more accessible to new users while keeping the flexibility and power that our experienced users have come to appreciate. Ultimately, we want to make CAF easier to learn and teach, with a coherent API design. Whether you are using CAF for cloud applications, micro services, edge computing or the IoT, we strive to make CAF a great choice for building distributed systems.
A secondary, but nonetheless important, goal is to provide more stability in the API and the ABI. We want to make it easier to package CAF for distributions while allowing us to continue to evolve the framework. To this end, we have carefully reviewed the API and have deprecated several features that we consider overly complex, error-prone or not in line with the overall design of CAF.
When looking at the changelog, you will notice that version 1.0 has more deprecations than any previous release and that we have touched APIs that are at the core of CAF. We have recently introduced the new mail API that replaces the old messaging API. Today, we would like to use this blog post to discuss further changes we have made and the reasons behind them.
aout
The aout
utility has been part of CAF for a long time. It provides a way to
print to the console from multiple actors in parallel without interleaving
output. However, the design of aout
tries to mimic std::cout
. With
std::format
on the horizon, we have decided to deprecate aout
in favor of a
printing API that is more in line with modern C++.
For printing to the console, CAF now provides a println
member function that
exists on actors and the actor system. The syntax is similar to std::format
and allows for type-safe printing of arbitrary values. When building CAF in
C++20 mode, println
will use std::format
under the hood. In C++17 mode, it
will use a custom implementation that supports only a subset of the formatting
options. In both cases, CAF will automatically pick up inspect
overloads to
render custom types.
When implementing event-based actors, a callback-based API is much more natural
than calling monitor
and then providing a special handler someplace else.
Thus, we have deprecated the single-argument monitor
function on event-based
actors in favor of a new overload that takes two arguments: the actor to monitor
and a callback function. The new overload also returns a disposable
object
that can be used to cancel the monitoring.
Alongside this change, we have also deprecated the set_down_handler
function,
since it becomes obsolete when using the new monitor
overload. The monitored
spawn flag has been deprecated for the same reason.
Binding the idle timeout to the actor behavior has been a source of confusion for many users. Consider this example:
caf::behavior my_actor(caf::event_based_actor* self) {
return {
// ... handlers ...
caf::after(10s) >> [] { /* ... */ }
};
}
Without looking at the documentation, it is not clear whether the timeout is
triggered once or repeatedly. Furthermore, it is unclear what kind of reference
will CAF keep to the actor. Will it keep the actor alive or will it only keep a
weak reference? The answer is that CAF will keep a strong reference to the actor
and trigger the timeout repeatedly. This is not always what users expect or
want. To trigger a timeout only once, users have to call self->become()
in the
callback to install a new behavior that does not include the timeout.
To address this issue, we have deprecated this use of after
and introduced a
new set_idle_handler(timeout, ref_type, repeat_type, callback)
function, where
timeout
is a duration, ref_type
is either strong_ref
or weak_ref
and
repeat_type
is either once
or repeat
. The callback
is a function that
will be called when the timeout triggers. This new approach separates the idle
timeout from the behavior and adds much clarity to the code.
Note that nothing changes for blocking actors. They still pass after
to
receive
as before (it simply configures how long the function may block). The
new function is only relevant for event-based actors.
In the past, CAF has provided special handlers for certain message types such as
exit_msg
as well as a catch-all handler for unexpected messages.
This design moves message handling logic away from the behavior, making it harder to understand the actor’s behavior at a glance. It also makes it harder to reason about an actor in general, as the behavior is now spread across multiple functions.
To address this issue, we have deprecated the special handlers. Instead, actors
can now simply handle these types of messages in their regular behavior and
provide a message handler for message
as a catch-all.
With CAF 1.0, we now include a fully-featured version of the new unit testing framework that we have been working on. Our team has been using this framework for a while now and we are very happy with it. We have ported all of our tests to the new framework and are deprecating the old testing framework as a result. The old headers are still available, but will be removed in version 2.0.
CAF provides multiple ways to spawn actors. We have supported and promoted the use of state-based actors for a while, but there have been several ways to implement them as well. The feedback we have received from users is that the current API is too complex and that it is not always clear which function to use.
To address this, we have streamlined the API for spawning actors and there are
only two ways we recommend to spawn actors now: as a function or using a state
class. For the latter, we have already shipped the new actor_from_state
utility in CAF 0.19.5.
All of our examples and guides have been updated to reflect this.
However, we have decided against deprecating the old spawn functions right away. This release already has a lot of deprecations in fundamental parts of CAF and we want to give users more time to adjust to the new API. However, we will deprecate the other spawn versions with CAF 2.0 and strongly encourage users to write new code only using the best practices we have outlined.
When encountering compiler errors with typed_actor
, it is often hard to
understand the compiler output due to the very long type names. To address this
we allow users to define traits for typed actors instead of passing the
signatures for the message handlers directly to the typed_actor
template.
The trait is simply a user-defined class that contains a type alias
signatures
, as shown in the following example:
struct cell_trait {
using signatures = caf::type_list<
caf::result<int32_t>(caf::get_atom),
caf::result<void>(caf::put_atom, int32_t)
>;
};
using cell_actor = caf::typed_actor<cell_trait>;
This quality-of-life change reduces the symbol name length in the compiler output and makes it easier to understand the error messages. While we recommend using this new approach for defining typed actors, the old way remains supported.
While mostly an invisible change, we have also worked on improving the ABI stability of CAF by better separating the implementation details from the public API. With this release, we are adopting a new versioning policy: only major releases may break the ABI (or API). We will continue to provide bug fixes and new features in minor releases, but changes that affect the ABI will only be made in major releases.
Last but not least, we have an important announcement to make regarding CAFcademy.
With CAFcademy, we have provided a free online archive of learning materials for CAF. Initially, we have also offered professional services around CAF on that website, before we have re-organized our commercial offerings around CAF with the launch of Interance in 2022.
With the company operating all things CAF now, we have decided to discontinue the CAFcademy brand, as it no longer serves a purpose. The articles from CAFcademy have been completely overhauled to reflect the current best practices and are available on https://www.interance.io/learning. The gems articles from CAFcademy have been moved to the manual and are now part of the official documentation. The line has always been blurry between the two and we believe that this consolidation will make it easier for users to find the information they need.
The CAFcademy website will be taken offline January 2025.