docs: API alternatives summary with registry and macro analysis#150
docs: API alternatives summary with registry and macro analysis#150ElFantasma wants to merge 7 commits intomainfrom
Conversation
… API Introduces a unified API redesign addressing #144, #145, and #129: - Handler<M> trait with per-message typed results (RPITIT, not object-safe) - Receiver<M>/Recipient<M> for type-erased cross-actor messaging - #[actor] proc macro that generates Handler<M> impls from #[handler] methods - Any-based named registry (register/whereis/unregister) - messages! declarative macro for defining message structs - Context<A> replaces ActorRef<A> in handler signatures - New examples: chat_room (Recipient), service_discovery (registry) - All existing examples migrated to new API
🤖 Codex Code ReviewReview Summary Findings
Nice work Possible next steps
Automated review by OpenAI Codex · custom prompt |
Greptile OverviewGreptile SummaryThis PR adds comprehensive documentation comparing 6 different API design approaches for the actor framework, alongside the actual implementation of Approach A (Handler + Recipient). The implementation includes major refactoring of the actor system from enum-based to per-message handlers, new registry functionality, and the Key Changes:
Critical Issue:
The code samples showing "With Otherwise: Confidence Score: 2/5
|
| Filename | Overview |
|---|---|
| docs/API_ALTERNATIVES_SUMMARY.md | New comprehensive documentation comparing 6 API approaches; however, extensively references actor_api! macro that doesn't exist in the codebase yet |
| concurrency/src/message.rs | New file introducing Message trait and declarative macros (messages!, send_messages!, request_messages!) for message definition |
| concurrency/src/registry.rs | New named registry implementation with Any-based type-erased storage, providing register/whereis/unregister API |
| concurrency/src/tasks/actor.rs | Major refactoring from enum-based Actor trait to Handler + Recipient pattern with envelope-based type erasure |
| macros/src/lib.rs | New #[actor] proc macro generating Handler implementations from #[send_handler]/#[request_handler] annotated methods |
| examples/chat_room/src/room.rs | New chat room example implementing Approach A with manual API trait (not using actor_api! macro mentioned in docs) |
| examples/service_discovery/src/main.rs | New example demonstrating named registry usage with Recipient for service discovery pattern |
Last reviewed commit: 36d03b4
| ### With `#[actor]` macro + `actor_api!` | ||
|
|
||
| <details> | ||
| <summary><b>room.rs</b> — macros eliminate both Handler and extension trait boilerplate</summary> | ||
|
|
||
| ```rust | ||
| use spawned_concurrency::actor_api; | ||
| use spawned_concurrency::send_messages; | ||
| use spawned_concurrency::request_messages; | ||
| use spawned_concurrency::tasks::{Actor, ActorRef, Context, Handler, Recipient}; | ||
| use spawned_macros::actor; | ||
|
|
||
| // -- Messages -- | ||
|
|
||
| send_messages! { | ||
| Say { from: String, text: String }; | ||
| Deliver { from: String, text: String }; | ||
| Join { name: String, inbox: Recipient<Deliver> } | ||
| } | ||
|
|
||
| request_messages! { | ||
| Members -> Vec<String> | ||
| } | ||
|
|
||
| // -- API -- | ||
|
|
||
| actor_api! { | ||
| pub ChatRoomApi for ActorRef<ChatRoom> { | ||
| send fn say(from: String, text: String) => Say; | ||
| send fn add_member(name: String, inbox: Recipient<Deliver>) => Join; | ||
| request async fn members() -> Vec<String> => Members; | ||
| } | ||
| } |
There was a problem hiding this comment.
The actor_api! macro referenced extensively in this section doesn't exist in the codebase. The actual implementation in examples/chat_room/src/room.rs uses manual trait definitions (lines 31-49), not the actor_api! macro shown here.
The documentation shows:
actor_api! {
pub ChatRoomApi for ActorRef<ChatRoom> {
send fn say(from: String, text: String) => Say;
// ...
}
}But the actual code manually defines the trait and impl without this macro.
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/API_ALTERNATIVES_SUMMARY.md
Line: 224:256
Comment:
The `actor_api!` macro referenced extensively in this section doesn't exist in the codebase. The actual implementation in `examples/chat_room/src/room.rs` uses manual trait definitions (lines 31-49), not the `actor_api!` macro shown here.
The documentation shows:
```rust
actor_api! {
pub ChatRoomApi for ActorRef<ChatRoom> {
send fn say(from: String, text: String) => Say;
// ...
}
}
```
But the actual code manually defines the trait and impl without this macro.
How can I resolve this? If you propose a fix, please make it concise.| | Dimension | Non-macro | With `#[actor]` macro + `actor_api!` | | ||
| |-----------|-----------|--------------------------------------| | ||
| | **Readability** | Each `impl Handler<M>` block is self-contained. You see the message type and return type in the trait bound. But many small impl blocks can feel scattered. | `#[send_handler]`/`#[request_handler]` attributes inside a single `#[actor] impl` block group all handlers together. `actor_api!` declares the caller-facing API in a compact block. Files read top-to-bottom: Messages → API → Actor. | | ||
| | **API at a glance** | Must scan all `impl Handler<M>` blocks to know what messages an actor handles. | The `actor_api!` block is the "at-a-glance" API surface — each line declares a method, its params, and the underlying message. | | ||
| | **Boilerplate** | One `impl Handler<M>` block per message × per actor. Message structs need manual `impl Message`. | `send_messages!`/`request_messages!` macros eliminate `Message` impls. `#[actor]` eliminates `Handler` impls. `actor_api!` reduces the extension trait + impl (~15 lines) to ~5 lines. | | ||
| | **main.rs expressivity** | Raw message structs: `room.send_request(Join { ... })` — explicit but verbose. | Extension traits: `alice.join_room(room.clone())` — reads like natural API calls. | | ||
| | **Circular dep solution** | `Recipient<M>` — room stores `Recipient<Deliver>`, user stores `Recipient<Say>`. Neither knows the other's concrete type. | Same mechanism. The macros don't change how type erasure works. | | ||
| | **Discoverability** | Standard Rust patterns. Any Rust developer can read `impl Handler<M>`. | `#[actor]` and `actor_api!` are custom — new developers need to learn what they do, but the patterns are common (Actix uses the same approach). | |
There was a problem hiding this comment.
The comparison table references actor_api! macro features that aren't implemented. The "With #[actor] macro + actor_api!" column describes functionality that doesn't exist in the codebase (grep shows actor_api only appears in this documentation file).
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/API_ALTERNATIVES_SUMMARY.md
Line: 376:383
Comment:
The comparison table references `actor_api!` macro features that aren't implemented. The "With `#[actor]` macro + `actor_api!`" column describes functionality that doesn't exist in the codebase (grep shows `actor_api` only appears in this documentation file).
How can I resolve this? If you propose a fix, please make it concise.| ## Macro Improvement Potential | ||
|
|
||
| Approach A's `actor_api!` macro eliminates extension trait boilerplate by generating a trait + impl from a compact declaration. Could similar macros reduce boilerplate in the other approaches? | ||
|
|
||
| ### Approach B: Protocol Traits — YES, significant potential | ||
|
|
||
| The bridge impls are structurally identical to what `actor_api!` already generates. Each bridge method just wraps `self.send(Msg { fields })`: | ||
|
|
||
| ```rust | ||
| // Current bridge boilerplate (~10 lines per actor) | ||
| impl ChatBroadcaster for ActorRef<ChatRoom> { | ||
| fn say(&self, from: String, text: String) -> Result<(), ActorError> { | ||
| self.send(Say { from, text }) | ||
| } | ||
| fn add_member(&self, name: String, inbox: Arc<dyn ChatParticipant>) -> Result<(), ActorError> { | ||
| self.send(Join { name, inbox }) | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| A variant of `actor_api!` could generate bridge impls for an existing trait: | ||
|
|
||
| ```rust | ||
| // Potential: impl-only mode for existing protocol traits | ||
| actor_api! { | ||
| impl ChatBroadcaster for ActorRef<ChatRoom> { | ||
| send fn say(from: String, text: String) => Say; | ||
| send fn add_member(name: String, inbox: Arc<dyn ChatParticipant>) => Join; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| This would use the same syntax but `impl Trait for Type` (no `pub`, no new trait) signals that we're implementing an existing trait. The protocol trait itself remains user-defined — it IS the contract, so it should stay hand-written. | ||
|
|
||
| **Impact:** Bridge boilerplate per actor drops from ~10 lines to ~4 lines. The protocol trait definition stays manual (by design). Combined with `#[actor]`, the total code for a protocol-based actor would be competitive with Approach A. | ||
|
|
||
| ### Approach C: Typed Wrappers — NO | ||
|
|
||
| The fundamental problem is the dual-channel architecture, not boilerplate. The `Clone` bound incompatibility between enum messages and `Recipient<M>` creates a structural split that macros can't paper over. Typed wrappers still hide `unreachable!()` branches internally. | ||
|
|
||
| ### Approach D: Derive Macro — N/A | ||
|
|
||
| This approach IS a macro. The `#[derive(ActorMessages)]` would generate message structs, `Message` impls, API wrappers, and `Handler<M>` delegation — subsuming what `actor_api!`, `send_messages!`, and `#[actor]` do separately. Adding `actor_api!` on top would be redundant. | ||
|
|
||
| ### Approach E: AnyActorRef — NO | ||
|
|
||
| You could wrap `send_any(Box::new(...))` in typed helper methods, but this provides false safety — the runtime dispatch can still fail. The whole point of AnyActorRef is erasing types; adding typed wrappers on top contradicts that. | ||
|
|
||
| ### Approach F: PID — PARTIAL | ||
|
|
||
| The registration boilerplate could be automated: | ||
|
|
||
| ```rust | ||
| // Current: manual registration per message type | ||
| room.register::<Say>(); | ||
| room.register::<Join>(); | ||
| room.register::<Members>(); | ||
|
|
||
| // Potential: derive-style auto-registration | ||
| #[actor(register(Say, Join, Members))] | ||
| impl ChatRoom { ... } | ||
| ``` | ||
|
|
||
| And `spawned::send(pid, Msg { ... })` could get ergonomic wrappers similar to `actor_api!`. But since `Pid` carries no type information, these wrappers can only provide ergonomics, not safety — a wrong Pid still causes a runtime error. | ||
|
|
||
| ### Summary | ||
|
|
||
| | Approach | Macro potential | What it would eliminate | Worth implementing? | | ||
| |----------|----------------|----------------------|---------------------| | ||
| | **B: Protocol Traits** | High | Bridge impl boilerplate | Yes — `actor_api!` impl-only mode | | ||
| | **C: Typed Wrappers** | None | N/A — structural problem | No | | ||
| | **D: Derive Macro** | N/A | Already a macro | N/A | | ||
| | **E: AnyActorRef** | None | Would add false safety | No | | ||
| | **F: PID** | Low-Medium | Registration ceremony | Maybe — ergonomics only | | ||
|
|
||
| **Takeaway:** Approach B is the only unimplemented approach that would meaningfully benefit from `actor_api!`-style macros. And the required change is small — adding an impl-only mode to the existing macro. This would make Approach B more competitive with Approach A on boilerplate, while retaining its testability advantage. |
There was a problem hiding this comment.
The entire "Macro Improvement Potential" section discusses how actor_api! could be extended to other approaches, but this macro doesn't exist yet. This creates confusion about what's actually implemented vs. what's proposed.
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/API_ALTERNATIVES_SUMMARY.md
Line: 945:1020
Comment:
The entire "Macro Improvement Potential" section discusses how `actor_api!` could be extended to other approaches, but this macro doesn't exist yet. This creates confusion about what's actually implemented vs. what's proposed.
How can I resolve this? If you propose a fix, please make it concise.
Summary
actor_api!macro and#[actor]improvementsactor_api!-style macros could reduce boilerplate in unimplemented approaches (B–F)Test plan
feat/actor-macro-registrychat_roomexample)