//! Process identifiers. //! //! A `Pid` is `(index, generation)`. The index is a slot in the scheduler's //! actor table; the generation increments every time that slot is reused. //! A stale `Pid` (correct index, wrong generation) is a detectable error, //! not a silent misdirection — solves the ABA problem without exhausting //! the PID space. #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct Pid { index: u32, generation: u32, } impl Pid { #[inline] pub const fn new(index: u32, generation: u32) -> Self { Self { index, generation } } #[inline] pub const fn index(self) -> u32 { self.index } #[inline] pub const fn generation(self) -> u32 { self.generation } } impl std::fmt::Debug for Pid { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Pid({}.{})", self.index, self.generation) } } impl std::fmt::Display for Pid { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "<{}.{}>", self.index, self.generation) } } // --------------------------------------------------------------------------- // Typed addresses (RFC 014 — typed addressable mailboxes). Phase 1: types only. // --------------------------------------------------------------------------- // // Two addressing modes layered over the plain `Pid` identity, distinguished by // their liveness semantics (RFC 014 §4.2): // // - `Addr` — direct, identity-bound. Keyed by the *actor type* `A`; its // message type is `A::Msg` (see [`Addressable`]). Bound to one specific actor // incarnation: once that actor dies the send fails (generation mismatch) and // is *not* redirected. This is the RFC's `Pid`, renamed — a generic // `Pid` cannot coexist with the non-generic `Pid` identity the rest of the // runtime is built on. // - `Name` — durable, re-resolving, location-transparent. A phantom-typed // key over the registry, re-resolved on *every* send, so it always reaches // whoever currently holds the name (surviving takeover; later, locating an // actor on another node). The clustering / BEAM-interop priority mode. // // Both are pure value tokens: an identity (or a name) plus a zero-sized phantom // that re-imposes the message type at the call site. The phantom is // `fn() -> T`, so a token is unconditionally `Copy + Send + Sync` and borrows // nothing from `T` — `T` is only ever a compile-time key. (This is also why the // trait impls below are hand-written: a `#[derive]` would wrongly demand // `T: Copy`/`Eq`/… on the phantom parameter.) use std::marker::PhantomData; /// An actor type with a single associated message type, so it can key an /// [`Addr`]. The raw channel layer has no such trait (actors are closures over /// channels), and `GenServer` is intrinsically multi-message and addressed via /// its own `ServerRef`; this is the minimal hook that gives a typed, /// identity-bound address to the actors that *do* have one message type. /// (RFC 014 §4.2, decision (a).) pub trait Addressable: 'static { /// The message this actor receives. An `Addr` delivers `Self::Msg`. type Msg: Send + 'static; } /// A direct, identity-bound address to a specific actor of type `A` /// (RFC 014's `Pid`): the plain [`Pid`] identity plus a phantom `A`, whose /// message type is `A::Msg`. A send through an `Addr` targets the exact /// incarnation the pid names; when that actor dies the send fails and is *not* /// redirected. For a durable, re-resolving address use [`Name`]. pub struct Addr { pid: Pid, _marker: PhantomData A>, } impl Addr { /// Wrap a plain identity as a typed address. Crate-internal on purpose: a /// public bare-`Pid` → typed-`Addr` mint is exactly the unchecked step the /// typed API exists to avoid. The sanctioned bare-pid path is the fallible /// `send_dyn` escape hatch (RFC 014 §4.6); typed addresses reach users via /// spawn / resolution. /// /// Phase 1 has no non-test constructor (this is a types-only commit); the /// `#[cfg(test)]` comes off in Phase 2 when the handle table / spawn mints /// these for real. #[cfg(test)] #[inline] pub(crate) const fn new(pid: Pid) -> Self { Self { pid, _marker: PhantomData } } /// The plain runtime identity this address points at. #[inline] pub const fn pid(self) -> Pid { self.pid } } impl Copy for Addr {} impl Clone for Addr { fn clone(&self) -> Self { *self } } impl PartialEq for Addr { fn eq(&self, other: &Self) -> bool { self.pid == other.pid } } impl Eq for Addr {} impl std::hash::Hash for Addr { fn hash(&self, state: &mut H) { self.pid.hash(state); } } impl std::fmt::Debug for Addr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Addr<{}>({})", std::any::type_name::(), self.pid) } } /// A durable, re-resolving address: a static name plus a phantom message type /// `M` (RFC 014's `Name`). Declared as a constant and shared freely: /// /// ```ignore /// const COUNTER: Name = Name::new("counter"); /// ``` /// /// Unlike [`Addr`], a `Name` is resolved through the registry on *every* send, /// so it always reaches whoever currently holds the name. pub struct Name { name: &'static str, _marker: PhantomData M>, } impl Name { /// Bind a static string as a typed name. `const`, so names live as /// associated constants at call sites. #[inline] pub const fn new(name: &'static str) -> Self { Self { name, _marker: PhantomData } } /// The underlying registry key. #[inline] pub const fn as_str(self) -> &'static str { self.name } } impl Copy for Name {} impl Clone for Name { fn clone(&self) -> Self { *self } } impl PartialEq for Name { fn eq(&self, other: &Self) -> bool { self.name == other.name } } impl Eq for Name {} impl std::hash::Hash for Name { fn hash(&self, state: &mut H) { self.name.hash(state); } } impl std::fmt::Debug for Name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Name<{}>({:?})", std::any::type_name::(), self.name) } } #[cfg(test)] mod typed_addr_tests { use super::*; // A stand-in actor type with one message type, exercising `Addressable`. struct Counter; struct CounterMsg; // used only as a phantom key; no variants needed impl Addressable for Counter { type Msg = CounterMsg; } fn msg_type_name() -> &'static str { std::any::type_name::() } #[test] fn addr_is_a_copyable_identity_token() { let a = Addr::::new(Pid::new(3, 1)); let b = a; // Copy, not move assert_eq!(a.pid(), Pid::new(3, 1)); assert_eq!(a, b); // Same index, different generation = different actor incarnation. assert_ne!(a, Addr::::new(Pid::new(3, 2))); assert!(format!("{a:?}").starts_with("Addr<")); } #[test] fn name_is_a_copyable_string_token() { const COUNTER: Name = Name::new("counter"); let n = COUNTER; // Copy assert_eq!(n.as_str(), "counter"); assert_eq!(n, COUNTER); assert_ne!(n, Name::::new("other")); assert!(format!("{n:?}").contains("\"counter\"")); } #[test] fn addressable_exposes_the_message_type() { assert!(msg_type_name::().ends_with("CounterMsg")); } // Tokens must be usable across threads (they key cross-node addressing // later): assert Send + Sync regardless of whether the keys are. #[test] fn tokens_are_send_sync_without_key_bounds() { fn assert_send_sync() {} assert_send_sync::>(); assert_send_sync::>(); } }