Skip to main content

atomr_core/actor/
sender.rs

1//! Typed `Sender` value passed alongside every message.
2//!
3//! Phase 1 of the full-port plan replaces the legacy
4//! `Box<dyn Any + Send>` sender erasure with this enum so that every
5//! reply path keeps compile-time type information about its origin.
6//! See `docs/idiomatic-rust.md` (P-1) and `docs/full-port-plan.md`
7//! Phase 1.
8//!
9//! The legacy `MessageEnvelope::sender: Option<Box<dyn Any + Send>>`
10//! and `Context::current_sender` fields are retained during the
11//! transition to keep existing call sites compiling. New code should
12//! use [`ActorRef::tell_from`] / [`Context::sender_typed`] /
13//! [`MessageEnvelope::with_typed_sender`].
14
15use std::sync::Arc;
16
17use super::actor_ref::UntypedActorRef;
18use super::path::ActorPath;
19use super::remote::RemoteRef;
20
21/// Typed identity of a message's sender.
22///
23/// Three variants:
24///
25/// * [`Sender::Local`] — the sender is an actor in this `ActorSystem`.
26/// * [`Sender::Remote`] — the sender lives in another `ActorSystem`,
27///   reached via remoting. Carries a path + a remote handle so replies
28///   can be serialized back without a `downcast`.
29/// * [`Sender::None`] — no sender attached (the analog of
30///   `IActorRef.NoSender`).
31#[derive(Clone)]
32#[non_exhaustive]
33#[derive(Default)]
34pub enum Sender {
35    Local(UntypedActorRef),
36    Remote {
37        path: ActorPath,
38        handle: Arc<dyn RemoteRef>,
39    },
40    #[default]
41    None,
42}
43
44impl Sender {
45    /// Path of the sender, if any.
46    pub fn path(&self) -> Option<&ActorPath> {
47        match self {
48            Sender::Local(r) => Some(r.path()),
49            Sender::Remote { path, .. } => Some(path),
50            Sender::None => None,
51        }
52    }
53
54    /// `true` iff the sender lives in another actor system.
55    pub fn is_remote(&self) -> bool {
56        matches!(self, Sender::Remote { .. })
57    }
58
59    /// `true` iff the sender slot is empty.
60    pub fn is_none(&self) -> bool {
61        matches!(self, Sender::None)
62    }
63
64    /// Borrow the local untyped ref, if this is a local sender.
65    pub fn local(&self) -> Option<&UntypedActorRef> {
66        if let Sender::Local(r) = self {
67            Some(r)
68        } else {
69            None
70        }
71    }
72}
73
74impl std::fmt::Debug for Sender {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        match self {
77            Sender::Local(r) => f.debug_tuple("Local").field(&r.path().to_string()).finish(),
78            Sender::Remote { path, .. } => f.debug_struct("Remote").field("path", &path.to_string()).finish(),
79            Sender::None => f.write_str("None"),
80        }
81    }
82}
83
84impl From<UntypedActorRef> for Sender {
85    fn from(r: UntypedActorRef) -> Self {
86        Sender::Local(r)
87    }
88}
89
90impl<M: Send + 'static> From<&super::actor_ref::ActorRef<M>> for Sender {
91    fn from(r: &super::actor_ref::ActorRef<M>) -> Self {
92        Sender::Local(r.as_untyped())
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn none_is_default() {
102        let s = Sender::default();
103        assert!(s.is_none());
104        assert!(!s.is_remote());
105        assert!(s.path().is_none());
106        assert!(s.local().is_none());
107    }
108}