//! 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::>();
}
}