aurum_actors/lib.rs
1// #![warn(missing_docs)]
2
3//! [`Aurum`](crate) implements a distributed actor model. Its purpose is to make distributed programming
4//! easy. [`Aurum`](crate) provides support for clustering, [CRDT] sharing among cluster members and [IoT]
5//! device tracking. It has excellent type safety and full [serde] support.
6//!
7//! # Prerequisite Knowledge
8//! We suggest having this knowledge before starting here:
9//! - A basic understanding of the [actor model]
10//! - Some knowledge of [serialization] and [serde]
11//! - [`async/await`] in Rust
12//!
13//! # Why Would You Want to Use [`Aurum`](crate)?
14//! ### Typed Actors
15//! Actor references are typed, meaning they can only receive messages of a specific type. Untyped
16//! actor references can receive messages of any type. Typed references add safety by turning some
17//! runtime errors into compile time errors, and eliminate the need for catch-all match branches
18//! that handle invalid messages. They also make code easier to read. Type information gives extra
19//! hints of an actor’s role in the program. Actor references are statically typed. The type of
20//! messages they receive must be defined at compile time. Generics are used to communicate the
21//! types of messages. Most distributed actor model implementations use untyped actors.
22//!
23//! ### Actor Interfaces
24//! It is not unusual for someone to want to store actor references in a data structure (for
25//! instance, to keep track of subscribers to events). Collections in Rust must be homogeneous,
26//! but there may be many different kinds of actors who want to subscribe to the same events.
27//! You cannot keep actor references receiving different types in the same data structure, nor is
28//! creating a different structure for every possible type desirable, nor do we want to create
29//! separate actors that relay event messages to the real subscriber under its preferred type. Actor
30//! interfaces allow the programmer to define a subset of message types receivable by an actor, and
31//! create actor references which receive only that subset.
32//!
33//! Actor interfaces are also useful if some of the messages an actor receives are serializable, and
34//! others are not. You can define a serializable subset, and create actor references that receive
35//! that subset. [`Aurum`](crate) provides annotations on message types to create automatic
36//! translations between subtypes and root message types.
37//!
38//! ### Forgeable References
39//! [`Aurum`](crate) was created to address fill niches in existing actor models like [Akka]. While
40//! [Akka] has support for both typed and untyped actors, typed actor references in [Akka] are not
41//! forgeable (i.e. they cannot be created independently). With [Akka]'s typed actors, you need a
42//! complex discovery protocol built underneath to acquire a reference to the actor for sending
43//! messages.
44//!
45//! A single system may have many actors running on it, so these actors receive their messages on
46//! same network socket. Messages are then routed locally using a registry. Each actor is registered
47//! on their socket with a unique identifier. Our problem comes from [Akka] using strings for its
48//! actor identifiers. Strings do not contain any type information about the actor, so how do we
49//! know what type to give our reference we just forged? It's not safe to just guess. [`Aurum`](crate) fixes
50//! this problem by embedding type information within actor identifiers. Identifiers consist of both
51//! a string and a type id for discriminating between identifiers with the same string name. The
52//! type safety of forged references is enforced at compile time. None of this type safety comes at
53//! the cost of performance.
54//!
55//! # Modules and Features
56//! [`Aurum`](crate) is a heavily modularized piece of software. [`cluster`](crate::cluster) is built
57//! on top of [`core`](crate::core) with no internal access. If you plan on building a library on top
58//! of [`Aurum`](crate), [`cluster`](crate::cluster) is a good reference.
59//! - [`cluster`](crate::cluster): Single data center clustering.
60//! - [`crdt`](crate::cluster::crdt): Intra-cluster CRDT sharing.
61//! - [`devices`](crate::cluster::devices): External node management. Useful for IoT.
62//! - [`core`](crate::core): The basics. Start here if you're new to [`Aurum`](crate) and want to
63//! learn how to use it.
64//! - [`testkit`](crate::testkit): Tools for testing actors and injecting failures.
65//!
66//! [`async/await`]: https://rust-lang.github.io/async-book/
67//! [serialization]: https://en.wikipedia.org/wiki/Serialization
68//! [actor model]: https://en.wikipedia.org/wiki/Actor_model
69//! [serde]: https://docs.serde.rs/serde/
70//! [CRDT]: https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type
71//! [Akka]: https://akka.io/
72//! [IoT]: https://en.wikipedia.org/wiki/Internet_of_things
73
74pub mod cluster;
75pub mod core;
76pub mod testkit;
77extern crate aurum_actors_macros;
78
79/// Creates a [`UnifiedType`](crate::core::UnifiedType) and implements traits for it.
80///
81/// This macro is central to how [`Aurum`](crate) functions. Because we have actor interfaces, there
82/// are multiple possible interpretations of a sequence of bytes. In order to deserialize messages
83/// correctly, we need to know how to interpret the bytes. We need to know what type was serialized.
84/// Information on what type the message is must be included within the message.
85/// [`UnifiedType`](crate::core::UnifiedType) instances are also used to safely deserialize
86/// [`Destination`](crate::core::Destination) instances, which contain a
87/// [`UnifiedType`](crate::core::UnifiedType) instance. Generic type information does not serialize,
88/// so if a [`Destination`](crate::core::Destination) is deserialized and interpreted with a
89/// generic type, we have to make sure that the [`ActorId`](crate::core::ActorId) and interface
90/// match that generic type as well as match each other, to make sure we do not create a
91/// [`Destination`](crate::core::Destination) that is invalid according to our type system.
92///
93/// Rust's [`TypeId`](std::any::TypeId) is not serializable, and does not come with any guarantees
94/// at all. We had to create our own system of reflection to fix this problem, but it is fairly easy
95/// to use. The [`UnifiedType`](crate::core::UnifiedType) created by this macro is an enum, whose
96/// variant represent all possible root message types and actor interfaces usable in an application.
97/// A type that is not in the [`UnifiedType`](crate::core::UnifiedType) may not be used in your
98/// application. [`Aurum`](crate) uses the [`Case`](crate::core::Case) trait to enforce this restriction.
99/// The end users must define a single [`UnifiedType`](crate::core::UnifiedType) for their
100/// application. DO NOT communicate between two [`Node`](crate::core::Node) instances with different
101/// types, things are bound to go wrong.
102///
103/// [`Case::VARIANT`](crate::core::Case::VARIANT) can be used to create instances of its
104/// [`UnifiedType`](crate::core::UnifiedType) from type information without needing to access the
105/// variants of that [`UnifiedType`](crate::core::UnifiedType), which are defined in the macro. This
106/// is how [`ActorRef`](crate::core::ActorRef) and [`Destination`](crate::core::Destination)
107/// construct [`UnifiedType`](crate::core::UnifiedType) instances for forging.
108///
109/// If you are writing a library built on top of
110/// [`Aurum`](crate), you can use [`Case`](crate::core::Case) to restrict your user's
111/// [`UnifiedType`](crate::core::UnifiedType) to make sure it is compatible with your messages.
112/// Users are required to list your dependent message types in their invocation of
113/// [`unify`](crate::unify). This is how [`cluster`](crate::cluster) would normally be implemented,
114/// but the [`Case`](crate::core::Case) bounds for [`UnifiedType`](crate::core::UnifiedType) include
115/// the dependent message types for [`cluster`](crate::cluster) for the sake on convenience.
116///
117/// ```
118/// use async_trait::async_trait;
119/// use aurum_actors::{unify, AurumInterface};
120/// use aurum_actors::core::{Actor, ActorContext, Case, UnifiedType};
121/// use im;
122/// use serde::{Serialize, Deserialize};
123///
124/// #[derive(AurumInterface, Serialize, Deserialize)]
125/// enum MsgTypeForSomeThirdPartyLibrary {
126/// #[aurum]
127/// Something(InterfaceForSomeThirdPartyLibrary)
128/// }
129///
130/// #[derive(Serialize, Deserialize)]
131/// struct InterfaceForSomeThirdPartyLibrary;
132///
133/// struct LibraryActor;
134/// #[async_trait]
135/// impl<U> Actor<U, MsgTypeForSomeThirdPartyLibrary> for LibraryActor
136/// where
137/// U: UnifiedType
138/// + Case<MsgTypeForSomeThirdPartyLibrary>
139/// + Case<InterfaceForSomeThirdPartyLibrary>
140/// {
141/// async fn recv(
142/// &mut self,
143/// ctx: &ActorContext<U, MsgTypeForSomeThirdPartyLibrary>,
144/// msg: MsgTypeForSomeThirdPartyLibrary
145/// ) {
146/// // logic
147/// }
148/// }
149///
150/// #[derive(AurumInterface)]
151/// #[aurum(local)]
152/// enum MyMsgType {
153/// First,
154/// Second
155/// }
156///
157/// #[derive(AurumInterface)]
158/// #[aurum(local)]
159/// enum MyOtherMsgType {
160/// Nonserializable(::std::fs::File),
161/// #[aurum]
162/// Serializable(InterfaceForSomeThirdPartyLibrary)
163/// }
164///
165/// unify! {
166/// unified_name = pub MyUnifiedType;
167/// root_types = {
168/// MyMsgType,
169/// MyOtherMsgType,
170/// MsgTypeForSomeThirdPartyLibrary
171/// };
172/// interfaces = {
173/// String,
174/// InterfaceForSomeThirdPartyLibrary
175/// };
176/// }
177/// ```
178///
179/// The syntax for [`unify`](crate::unify) is structured as an unordered set of key-value pairs
180/// terminated by a semicolon. No key should be defined more than once. The keys are:
181/// - `unified_name`: Required. Give an optional visibility and an identifier to name the
182/// [`UnifiedType`](crate::core::UnifiedType).
183/// - `root_types`: Optional. Contained in braces, it is an unordered, comma-terminated set of types
184/// that implement [`RootMessage`](crate::core::RootMessage).
185/// - `interfaces`: Optional. Contained in braces, it is an unordered, comma-terminated set of types
186/// which are used as interfaces to root types. These are types annotated with `#[aurum]` on
187/// invocations of [`AurumInterface`](crate::AurumInterface).
188pub use aurum_actors_macros::unify;
189
190/// Implements [`RootMessage`](crate::core::RootMessage) and other traits for a root message type.
191///
192/// This derive macro is applicable to enums only. It generates all the necessary implementations
193/// for the message type to interact with the [`UnifiedType`](crate::core::UnifiedType) and produce
194/// interfaces. It uses the `#[aurum]` atrribute to configure interfaces. The `#[aurum]` attribute
195/// has a single optional argument: `local`. Use `#[aurum(local)]` to notify [`AurumInterface`] that
196/// the interface is exclusively local.
197///
198/// ```
199/// use aurum_actors::AurumInterface;
200///
201/// type StrStaticRef = &'static str;
202///
203/// #[derive(AurumInterface)]
204/// #[aurum(local)]
205/// enum DiverseMsgType {
206/// #[aurum]
207/// MyStringInterface(String),
208/// #[aurum(local)]
209/// MyNonserializableInterface(StrStaticRef),
210/// MyOtherMsg(u128),
211/// }
212/// ```
213///
214/// In this example, because one of the message options is not serializable, the message type as a
215/// whole is not serializable. However, you can use an
216/// [`ActorRef<MyUnifiedType, String>`](crate::core::ActorRef) to send a string from a remote
217/// machine to whatever actor uses this message type. You can also create a
218/// [`LocalRef<&’static str>`](crate::core::LocalRef), but not a usable
219/// [`ActorRef`](crate::core::ActorRef).
220///
221/// [`AurumInterface`] creates an implementation of [`RootMessage`](crate::core::RootMessage) for
222/// the type it is invoked on. The [`RootMessage`](crate::core::RootMessage) implementation is
223/// blanketed over all types implementing [`UnifiedType`](crate::core::UnifiedType). The blanket is
224/// bounded by [`Case`](crate::core::Case) implementation for the root type and each remote
225/// interface. In the example, the implementation is bounded by
226/// [`Case<MyMsgType>`](crate::core::Case) and [`Case<String>`](crate::core::Case) but not
227/// [`Case<&'static str>`](crate::core::Case).
228///
229/// [`AurumInterface`] implements [`From`](std::convert::From) on every interface type, local or
230/// not. In the example, [`From<String>`](std::convert::From) and
231/// [`From<&'static str>`](std::convert::From) are implemented for `MyMsgType`.
232///
233/// This macro's parsing is a work in progress, so for now you will need to create aliases for some
234/// types.
235pub use aurum_actors_macros::AurumInterface;