acty/lib.rs
1//! # Acty
2//!
3//! Acty is a lightweight and high-performance Actor framework built on top of Tokio.
4//! It adheres to the core philosophies of "everything is an Actor" and "sender-driven lifecycle,"
5//! aiming to provide a simple, safe, and easy-to-use concurrency model.
6//!
7//! ## Core Concepts
8//!
9//! - **Actor**: Any type that implements the [`Actor`] trait is an Actor. Its core is the `run` method, which contains all the business logic and state management.
10//! - **Lifecycle**: An Actor's lifecycle is determined by its message senders (the `Outbox`). When all associated `Outbox` instances are dropped, the Actor's message channel (`inbox`) closes, and the Actor task gracefully terminates. This design eliminates the complexity of manually sending "stop" messages.
11//! - **Messaging**: By calling the [`ActorExt::start`] method to launch an Actor, you receive an [`UnboundedOutbox`] (or [`BoundedOutbox`]). This `Outbox` is the sole handle for sending messages to the Actor.
12//! - **State Management**: The Actor's state usually resides as local variables within the asynchronous scope of its `run` method, rather than as fields of the structure. This ensures cleaner state isolation and ownership management.
13//!
14//! ## Result Communication (Backchannel)
15//!
16//! An Actor can send its final computation result back to the caller through mechanisms like `oneshot` channels before its task ends.
17//!
18//! ### Example: An Actor that concatenates strings
19//! ```rust
20//! use std::pin::pin;
21//! use std::fmt::Write;
22//! use futures::{Stream, StreamExt};
23//! use acty::{Actor, ActorExt, AsyncClose};
24//!
25//! // 1. Define the Actor structure. It holds a oneshot::Sender to return the result.
26//! struct MyActor {
27//! result: tokio::sync::oneshot::Sender<String>,
28//! }
29//!
30//! // 2. Implement the Actor trait, defining the message type and core logic.
31//! impl Actor for MyActor {
32//! type Message = String;
33//!
34//! async fn run(self, inbox: impl Stream<Item = Self::Message> + Send) {
35//! // Pin the inbox to the stack for asynchronous iteration.
36//! let mut inbox = pin!(inbox);
37//!
38//! // The Actor's internal state is defined and managed within the run method.
39//! let mut article = String::new();
40//! let mut count = 1;
41//!
42//! // Loop through messages until the inbox closes.
43//! while let Some(msg) = inbox.next().await {
44//! write!(article, "part {}: {}\n", count, msg.as_str()).unwrap();
45//! count += 1;
46//! }
47//!
48//! // When all Outboxes are dropped, the loop ends. Send the final result here.
49//! // Sending might fail if the receiver has already given up waiting, so we ignore the error.
50//! let _ = self.result.send(article);
51//! }
52//! }
53//!
54//! #[tokio::main]
55//! async fn main() {
56//! // 3. Create the channel for receiving the result.
57//! let (tx, rx) = tokio::sync::oneshot::channel();
58//!
59//! // 4. Instantiate and launch the Actor, getting the Outbox handle.
60//! let my_actor = MyActor { result: tx }.start();
61//!
62//! // 5. Send messages to the Actor. Ignore potential send errors caused by early shutdown.
63//! my_actor.send("It's a good day today.".to_string()).unwrap_or(());
64//! my_actor.send("Let's have some tea first.".to_string()).unwrap_or(());
65//!
66//! // 6. Close the outbox. Since it's the last Outbox, this triggers the Actor's shutdown.
67//! // .close().await waits for the Actor task to fully complete.
68//! my_actor.close().await;
69//!
70//! // 7. Wait for and retrieve the Actor's final execution result.
71//! assert_eq!(
72//! "part 1: It's a good day today.\npart 2: Let's have some tea first.\n",
73//! rx.await.expect("Actor task failed to send the result")
74//! );
75//! }
76//! ```
77//!
78//! For more examples, please see the examples directory.
79
80mod actor;
81mod close;
82mod launch;
83mod outbox;
84mod start;
85
86pub use {
87 actor::Actor, close::AsyncClose, launch::Launch, outbox::BoundedOutbox,
88 outbox::UnboundedOutbox, start::ActorExt,
89};