Skip to main content

behavior/actor/
creation.rs

1//! Staged fresh-actor creation capabilities.
2
3use core::future::Future;
4use core::marker::PhantomData;
5
6use super::addressing::Address;
7use crate::next::Never;
8use crate::{Behavior, Protocol};
9
10/// Behavior-owned provenance for a staged fresh actor creation request.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum CreationKind<N> {
13    /// An initial or ordinary later birth.
14    Birth,
15    /// A fresh successor incarnation requested by a replacement protocol.
16    ReplacementIncarnation {
17        /// The exact child incarnation this fresh actor is intended to replace.
18        replaces: N,
19    },
20}
21
22impl<N> CreationKind<N> {
23    #[must_use]
24    pub const fn replacement_of(replaces: N) -> Self {
25        Self::ReplacementIncarnation { replaces }
26    }
27}
28
29/// A staged request to establish a fresh child at a creator-local nonce.
30///
31/// The nonce is a routing and correlation key, not an actor identity or proof
32/// of freshness. The kind is Behavior-owned intent; a runtime's typed creation
33/// resolution is the corresponding committed fact. Replacement at an existing
34/// address is deliberately absent; stable identity is derived with a proxy
35/// actor.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct Create<A: Address, New> {
38    pub nonce: A::Nonce,
39    pub child: New,
40    pub kind: CreationKind<A::Nonce>,
41}
42
43impl<A: Address, New> Create<A, New> {
44    #[must_use]
45    pub const fn new(nonce: A::Nonce, child: New, kind: CreationKind<A::Nonce>) -> Self {
46        Self { nonce, child, kind }
47    }
48
49    #[must_use]
50    pub const fn birth(nonce: A::Nonce, child: New) -> Self {
51        Self::new(nonce, child, CreationKind::Birth)
52    }
53
54    #[must_use]
55    pub const fn replacement_incarnation(nonce: A::Nonce, replaces: A::Nonce, child: New) -> Self {
56        Self::new(nonce, child, CreationKind::replacement_of(replaces))
57    }
58}
59
60/// Static interpreter capability for installing one concrete child behavior.
61///
62/// An interpreter implements this trait separately for every concrete child
63/// protocol it can install. Heterogeneous child sums require all applicable
64/// implementations through recursive static bounds, so unsupported alternatives
65/// fail to compile instead of falling through to a registry or erased path. The
66/// returned future is sendable so an interpreter may remain inside a
67/// thread-safe recursive driver future.
68pub trait InstallBirth<A: Address, C: Behavior, Output, Error>
69where
70    C::Protocol: Protocol<Addr = A>,
71{
72    /// Install and commit exactly the supplied concrete creation.
73    ///
74    /// # Errors
75    /// Returns the interpreter's typed allocation, initialization, or commit
76    /// failure without binding the requested nonce.
77    fn install_birth(
78        &mut self,
79        creation: Create<A, C>,
80    ) -> impl Future<Output = Result<Output, Error>> + Send;
81}
82
83/// Exhaustive static dispatch of one creation-only child sum.
84///
85/// This is an interpreter-facing derived construction, not another actor-model
86/// operation. Implementations must preserve the nonce and provenance and call
87/// exactly one concrete [`InstallBirth`] implementation. [`ChildChoice`]
88/// provides the closed recursive heterogeneous sum. Dispatch futures are
89/// sendable; heterogeneous sums therefore require sendable alternatives,
90/// creator-local nonces, and installers.
91pub trait DispatchBirth<A: Address, Installer, Output, Error>: Sized {
92    /// Select exactly one concrete installer while preserving creation data.
93    ///
94    /// # Errors
95    /// Returns the selected concrete installer's typed failure unchanged.
96    fn dispatch_birth(
97        self,
98        nonce: A::Nonce,
99        kind: CreationKind<A::Nonce>,
100        installer: &mut Installer,
101    ) -> impl Future<Output = Result<Output, Error>> + Send;
102}
103
104/// One alternative in a closed, recursively composed child-creation sum.
105///
106/// `Head` is one concrete child behavior and `Tail` is the remaining closed
107/// sum. This is a creation choice only: it is not a behavior, message
108/// envelope, registry, or runtime dispatch mechanism.
109///
110/// Every alternative requires a concrete installer; incomplete interpreter
111/// support is rejected statically:
112///
113/// ```compile_fail
114/// use behavior::{
115///     Actions, Behavior, BehaviorActed, ChildChoice, Create, DispatchBirth,
116///     InstallBirth, MailAddr, Never, NoBirths, Protocol, User,
117/// };
118///
119/// struct First;
120/// struct Second;
121///
122/// macro_rules! inert {
123///     ($child:ty) => {
124///         impl Protocol for $child {
125///             type Addr = MailAddr;
126///             type Msg = Never;
127///         }
128///         impl Behavior for $child {
129///             type Event = User<MailAddr, Never>;
130///             type Sends = Vec<Never>;
131///             type Ph = Never;
132///             type Error = Never;
133///             type Birth = NoBirths;
134///
135///             fn transition(
136///                 &mut self,
137///                 _: behavior::ActiveTurn,
138///                 event: Self::Event,
139///             ) -> BehaviorActed<Self> {
140///                 match event.message {}
141///             }
142///         }
143///     };
144/// }
145/// inert!(First);
146/// inert!(Second);
147///
148/// struct Incomplete;
149/// impl InstallBirth<MailAddr, First, (), Never> for Incomplete {
150///     async fn install_birth(
151///         &mut self,
152///         _: Create<MailAddr, First>,
153///     ) -> Result<(), Never> {
154///         Ok(())
155///     }
156/// }
157///
158/// type Both = ChildChoice<First, ChildChoice<Second, Never>>;
159/// fn require_complete<T: DispatchBirth<MailAddr, Incomplete, (), Never>>() {}
160/// require_complete::<Both>();
161/// ```
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub enum ChildChoice<Head, Tail> {
164    /// Select the concrete child at this position.
165    Head(Head),
166    /// Select one concrete child from the remaining alternatives.
167    Tail(Tail),
168}
169
170mod sealed {
171    pub trait ChildProduct {}
172}
173
174/// The empty heterogeneous creation product.
175#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
176pub struct NoChildren;
177
178impl sealed::ChildProduct for NoChildren {}
179
180/// One creation appended to an ordered heterogeneous child product.
181pub struct ChildCons<A: Address, C, Earlier> {
182    creation: Create<A, C>,
183    earlier: Earlier,
184}
185
186impl<A: Address, C, Earlier> sealed::ChildProduct for ChildCons<A, C, Earlier> {}
187
188/// A pure, ordered heterogeneous product of staged direct-child creations.
189///
190/// This value owns no mailbox, address, runtime actor, or lifecycle. Explicit
191/// creator-local nonces remain caller policy. Conversion rejects duplicates
192/// within this product before producing any `Actions` value; the interpreter
193/// must still reject collisions with already established children.
194pub struct Children<A: Address, Product = NoChildren> {
195    product: Product,
196    address: PhantomData<fn() -> A>,
197}
198
199/// Rejection while converting a heterogeneous child product into creations.
200#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
201pub enum ChildrenError<N> {
202    /// Two declarations selected the same creator-local routing nonce.
203    #[error("duplicate creator-local child nonce")]
204    DuplicateNonce {
205        /// The colliding nonce; no creation product was returned.
206        nonce: N,
207    },
208}
209
210/// Closed recursive conversion implemented only by Bombay child products.
211pub trait ChildProduct<A: Address>: sealed::ChildProduct + Sized {
212    /// Closed sum containing exactly the concrete child behavior types.
213    type Choice;
214
215    #[doc(hidden)]
216    fn stage(
217        self,
218    ) -> Result<(Vec<Create<A, Self::Choice>>, Vec<A::Nonce>), ChildrenError<A::Nonce>>;
219}
220
221impl<A: Address> ChildProduct<A> for NoChildren {
222    type Choice = Never;
223
224    fn stage(
225        self,
226    ) -> Result<(Vec<Create<A, Self::Choice>>, Vec<A::Nonce>), ChildrenError<A::Nonce>> {
227        Ok((Vec::new(), Vec::new()))
228    }
229}
230
231impl<A, C, Earlier> ChildProduct<A> for ChildCons<A, C, Earlier>
232where
233    A: Address,
234    C: Behavior,
235    C::Protocol: Protocol<Addr = A>,
236    Earlier: ChildProduct<A>,
237{
238    type Choice = ChildChoice<C, Earlier::Choice>;
239
240    fn stage(
241        self,
242    ) -> Result<(Vec<Create<A, Self::Choice>>, Vec<A::Nonce>), ChildrenError<A::Nonce>> {
243        let (earlier, mut nonces) = self.earlier.stage()?;
244        if nonces.contains(&self.creation.nonce) {
245            return Err(ChildrenError::DuplicateNonce {
246                nonce: self.creation.nonce,
247            });
248        }
249        let mut creates = earlier
250            .into_iter()
251            .map(|creation| {
252                Create::new(
253                    creation.nonce,
254                    ChildChoice::Tail(creation.child),
255                    creation.kind,
256                )
257            })
258            .collect::<Vec<_>>();
259        nonces.push(self.creation.nonce);
260        creates.push(Create::new(
261            self.creation.nonce,
262            ChildChoice::Head(self.creation.child),
263            self.creation.kind,
264        ));
265        Ok((creates, nonces))
266    }
267}
268
269impl<A: Address> Children<A, NoChildren> {
270    /// Start an empty heterogeneous creation product.
271    #[must_use]
272    pub const fn new() -> Self {
273        Self {
274            product: NoChildren,
275            address: PhantomData,
276        }
277    }
278}
279
280impl<A: Address> Default for Children<A, NoChildren> {
281    fn default() -> Self {
282        Self::new()
283    }
284}
285
286impl<A: Address, Product> Children<A, Product> {
287    /// Append one complete staged creation, preserving its provenance.
288    #[must_use]
289    pub fn create<C>(self, creation: Create<A, C>) -> Children<A, ChildCons<A, C, Product>>
290    where
291        C: Behavior,
292        C::Protocol: Protocol<Addr = A>,
293    {
294        Children {
295            product: ChildCons {
296                creation,
297                earlier: self.product,
298            },
299            address: PhantomData,
300        }
301    }
302
303    /// Append one ordinary fresh-birth request.
304    #[must_use]
305    pub fn child<C>(self, nonce: A::Nonce, child: C) -> Children<A, ChildCons<A, C, Product>>
306    where
307        C: Behavior,
308        C::Protocol: Protocol<Addr = A>,
309    {
310        self.create(Create::birth(nonce, child))
311    }
312}
313
314impl<A, Product> Children<A, Product>
315where
316    A: Address,
317    Product: ChildProduct<A>,
318{
319    /// Validate the shared nonce domain and produce ordered creation requests.
320    ///
321    /// # Errors
322    /// Returns [`ChildrenError::DuplicateNonce`] without returning a partial
323    /// creation vector when any two declarations in this product share a
324    /// nonce. Existing child-namespace collisions remain interpreter-owned
325    /// creation rejections.
326    pub fn into_creates(self) -> Result<Vec<Create<A, Product::Choice>>, ChildrenError<A::Nonce>> {
327        self.product.stage().map(|(creates, _)| creates)
328    }
329}
330
331impl<A, Head, Tail, Installer, Output, Error> DispatchBirth<A, Installer, Output, Error>
332    for ChildChoice<Head, Tail>
333where
334    A: Address,
335    A::Nonce: Send,
336    Head: Behavior + Send,
337    Head::Protocol: Protocol<Addr = A>,
338    Tail: DispatchBirth<A, Installer, Output, Error> + Send,
339    Installer: InstallBirth<A, Head, Output, Error> + Send,
340{
341    async fn dispatch_birth(
342        self,
343        nonce: A::Nonce,
344        kind: CreationKind<A::Nonce>,
345        installer: &mut Installer,
346    ) -> Result<Output, Error> {
347        match self {
348            Self::Head(child) => {
349                installer
350                    .install_birth(Create::new(nonce, child, kind))
351                    .await
352            }
353            Self::Tail(tail) => tail.dispatch_birth(nonce, kind, installer).await,
354        }
355    }
356}
357
358impl<A, Installer, Output, Error> DispatchBirth<A, Installer, Output, Error> for Never
359where
360    A: Address,
361{
362    fn dispatch_birth(
363        self,
364        _nonce: A::Nonce,
365        _kind: CreationKind<A::Nonce>,
366        _installer: &mut Installer,
367    ) -> impl Future<Output = Result<Output, Error>> + Send {
368        async move { match self {} }
369    }
370}
371
372impl<A, C, Installer, Output, Error> DispatchBirth<A, Installer, Output, Error> for C
373where
374    A: Address,
375    C: Behavior,
376    C::Protocol: Protocol<Addr = A>,
377    Installer: InstallBirth<A, C, Output, Error>,
378{
379    fn dispatch_birth(
380        self,
381        nonce: A::Nonce,
382        kind: CreationKind<A::Nonce>,
383        installer: &mut Installer,
384    ) -> impl Future<Output = Result<Output, Error>> + Send {
385        installer.install_birth(Create::new(nonce, self, kind))
386    }
387}
388
389/// A type-level description of a behavior's creation capability.
390pub trait BirthMode {
391    type Child;
392}
393
394/// This behavior cannot emit child births.
395#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
396pub struct NoBirths;
397
398impl BirthMode for NoBirths {
399    type Child = Never;
400}
401
402/// This behavior may emit births of `C`.
403#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
404pub struct Births<C>(PhantomData<fn() -> C>);
405
406impl<C> BirthMode for Births<C> {
407    type Child = C;
408}
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413    use crate::{Actions, BehaviorActed, MailAddr, NoBirths, User};
414
415    struct Child;
416
417    impl behavior::Protocol for Child {
418        type Addr = MailAddr;
419        type Msg = u8;
420    }
421
422    impl Behavior for Child {
423        type Protocol = Self;
424        type Event = User<MailAddr, u8>;
425        type Sends = Vec<Never>;
426        type Ph = Never;
427        type Error = Never;
428        type Birth = NoBirths;
429
430        fn transition(&mut self, _: crate::ActiveTurn, _: Self::Event) -> BehaviorActed<Self> {
431            Ok(Actions::cont())
432        }
433    }
434
435    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
436    enum InstallError {
437        Refused,
438    }
439
440    struct RecordingInstaller {
441        calls: usize,
442        observed: Vec<(u64, CreationKind<u64>)>,
443        result: Result<u32, InstallError>,
444    }
445
446    impl InstallBirth<MailAddr, Child, u32, InstallError> for RecordingInstaller {
447        async fn install_birth(
448            &mut self,
449            creation: Create<MailAddr, Child>,
450        ) -> Result<u32, InstallError> {
451            self.calls += 1;
452            self.observed.push((creation.nonce, creation.kind));
453            self.result
454        }
455    }
456
457    fn installer(result: Result<u32, InstallError>) -> RecordingInstaller {
458        RecordingInstaller {
459            calls: 0,
460            observed: Vec::new(),
461            result,
462        }
463    }
464
465    fn assert_send<T: Send>(_: &T) {}
466
467    #[test]
468    fn empty_child_product_stages_no_creations_or_nonces() {
469        let (creations, nonces) = <NoChildren as ChildProduct<MailAddr>>::stage(NoChildren)
470            .expect("an empty child product is valid");
471        assert!(creations.is_empty());
472        assert!(nonces.is_empty());
473    }
474
475    #[tokio::test]
476    async fn concrete_child_dispatches_once_with_exact_nonce_provenance_and_output() {
477        let mut installer = installer(Ok(91));
478        let future = Child.dispatch_birth(
479            17,
480            CreationKind::ReplacementIncarnation { replaces: 8 },
481            &mut installer,
482        );
483        assert_send(&future);
484        let result = future.await;
485
486        assert_eq!(result, Ok(91));
487        assert_eq!(installer.calls, 1);
488        assert_eq!(
489            installer.observed,
490            [(17, CreationKind::ReplacementIncarnation { replaces: 8 })]
491        );
492    }
493
494    #[tokio::test]
495    async fn concrete_child_returns_the_exact_installer_error_without_retry() {
496        let mut installer = installer(Err(InstallError::Refused));
497        let future = Child.dispatch_birth(23, CreationKind::replacement_of(19), &mut installer);
498        assert_send(&future);
499        let result = future.await;
500
501        assert_eq!(result, Err(InstallError::Refused));
502        assert_eq!(installer.calls, 1);
503        assert_eq!(installer.observed, [(23, CreationKind::replacement_of(19))]);
504    }
505}