Skip to main content

behavior

Attribute Macro behavior 

Source
#[behavior]
Expand description

Generate the nominal protocol, closed effect products, and exact Behavior wiring for an inherent impl. addr and message are required. Omitting sends, births, or error selects the capability-free NoSends, NoBirths, or Never type respectively.

A sends = { lane: Product } declaration generates ActorSends, one distinct ActorSendsLane selector per field, and structural SendEffects, SendsFor, and InterpretSends implementations. It also generates an ActorActions extension trait with one fluent send_lane method per named lane. Each method delegates to AppendSend, changing only the send leg while preserving creations and the exact next-behavior verdict. A births = { lane: Child } declaration generates ActorChildren as the exact recursive ChildChoice produced by Children calls in declaration order. It also generates ActorChildrenRoutes, containing one nominally distinct ChildRoute per declared role. A route is the single typed source for staging that role’s creation and addressing its creator-local recipient. Creation remains an authored Children value and is never performed by the macro.

Invalid receivers are rejected at compile time.

ⓘ
use behavior::{Actions, BehaviorActed, MailAddr};

struct Invalid;
#[behavior::behavior(
    addr = MailAddr,
    message = u8,
)]
impl Invalid {
    fn init(&self) -> BehaviorActed<Self> {
        Ok(Actions::cont())
    }
    fn receive(&mut self, _: MailAddr, _: u8) -> BehaviorActed<Self> {
        Ok(Actions::cont())
    }
}

Missing receive methods are rejected by the macro itself:

ⓘ
use behavior::MailAddr;
struct Missing;
#[behavior::behavior(
    addr = MailAddr,
    message = u8,
)]
impl Missing {
}

Async behavior methods cannot introduce an erased or alternate execution path:

ⓘ
use behavior::{Actions, BehaviorActed, MailAddr};
struct Async;
#[behavior::behavior(
    addr = MailAddr,
    message = u8,
)]
impl Async {
    async fn init(&mut self) -> BehaviorActed<Self> {
        Ok(Actions::cont())
    }
    fn receive(&mut self, _: MailAddr, _: u8) -> BehaviorActed<Self> {
        Ok(Actions::cont())
    }
}

Undeclared send lanes have no selector and cannot be emitted:

ⓘ
use behavior::{Actions, BehaviorActed, MailAddr, SendEffects};
struct Sender;
#[behavior::behavior(addr = MailAddr, message = (), sends = { replies: Vec<u8> })]
impl Sender {
    fn receive(&mut self, _: MailAddr, _: ()) -> BehaviorActed<Self> {
        let mut sends = SenderSends::empty();
        sends.send::<_, SenderSendsUndeclared>(1);
        Ok(Actions::send(sends))
    }
}

Generated lane methods accept only inputs supported by that lane:

ⓘ
use behavior::{Actions, BehaviorActed, MailAddr};
struct Sender;
#[behavior::behavior(addr = MailAddr, message = (), sends = { replies: Vec<u8> })]
impl Sender {
    fn receive(&mut self, _: MailAddr, _: ()) -> BehaviorActed<Self> {
        Ok(Actions::cont().send_replies("not a u8"))
    }
}

A child absent from the declared closed birth product cannot be created:

ⓘ
use behavior::{Actions, BehaviorActed, Create, MailAddr};
struct Declared;
struct Other;
struct Root;
#[behavior::behavior(addr = MailAddr, message = (), births = { declared: Declared })]
impl Root {
    fn receive(&mut self, _: MailAddr, _: ()) -> BehaviorActed<Self> {
        Ok(Actions::create(vec![Create::birth(1, Other)]))
    }
}

Every generated send lane remains a separate interpreter obligation:

ⓘ
use behavior::{BehaviorActed, Delivery, InterpretDelivery, InterpretSends, MailAddr,
    MessageProtocol, Recipient, SendInterpreter};
struct Root;
type First = MessageProtocol<MailAddr, u8>;
type Second = MessageProtocol<MailAddr, u16>;
#[behavior::behavior(addr = MailAddr, message = (), sends = {
    first: Vec<Delivery<First>>,
    second: Vec<Delivery<Second>>,
})]
impl Root {
    fn receive(&mut self, _: MailAddr, _: ()) -> BehaviorActed<Self> {
        Ok(behavior::Actions::cont())
    }
}
struct Incomplete;
impl SendInterpreter for Incomplete { type Error = (); }
impl InterpretDelivery<First> for Incomplete {
    fn interpret_delivery(&mut self, _: Delivery<First>) -> impl core::future::Future<Output = Result<(), ()>> + Send {
        async { Ok(()) }
    }
}
fn require_complete()
where
    RootSends: InterpretSends<Incomplete, behavior::User<MailAddr, ()>, behavior::Here>,
{}

Generated child products likewise require an installer for every declared alternative:

ⓘ
use behavior::{BehaviorActed, Create, DispatchBirth, InstallBirth, MailAddr, Never};
struct First;
struct Second;
#[behavior::behavior(addr = MailAddr, message = Never)]
impl First {
    fn receive(&mut self, _: MailAddr, message: Never) -> BehaviorActed<Self> { match message {} }
}
#[behavior::behavior(addr = MailAddr, message = Never)]
impl Second {
    fn receive(&mut self, _: MailAddr, message: Never) -> BehaviorActed<Self> { match message {} }
}
struct Root;
#[behavior::behavior(addr = MailAddr, message = (), births = {
    first: First,
    second: Second,
})]
impl Root {
    fn receive(&mut self, _: MailAddr, _: ()) -> BehaviorActed<Self> {
        Ok(behavior::Actions::cont())
    }
}
struct Incomplete;
impl InstallBirth<MailAddr, First, (), Never> for Incomplete {
    async fn install_birth(&mut self, _: Create<MailAddr, First>) -> Result<(), Never> { Ok(()) }
}
fn require_complete<T: DispatchBirth<MailAddr, Incomplete, (), Never>>() {}
require_complete::<RootChildren>();

A generated child route accepts only its declared behavior:

ⓘ
use behavior::{BehaviorActed, Children, MailAddr, Never};
struct Declared;
#[behavior::behavior(addr = MailAddr, message = Never)]
impl Declared {
    fn receive(&mut self, _: MailAddr, message: Never) -> BehaviorActed<Self> {
        match message {}
    }
}
struct Other;
#[behavior::behavior(addr = MailAddr, message = Never)]
impl Other {
    fn receive(&mut self, _: MailAddr, message: Never) -> BehaviorActed<Self> {
        match message {}
    }
}
struct Root;
#[behavior::behavior(addr = MailAddr, message = Never, births = { worker: Declared })]
impl Root {
    fn receive(&mut self, _: MailAddr, message: Never) -> BehaviorActed<Self> {
        match message {}
    }
}
let routes = RootChildrenRoutes::new(1);
let _ = Children::<MailAddr>::new().child_at(routes.worker, Other);

Two declared roles remain distinct even when they use the same behavior:

ⓘ
use behavior::{BehaviorActed, ChildRoute, MailAddr, Never};
struct Worker;
#[behavior::behavior(addr = MailAddr, message = Never)]
impl Worker {
    fn receive(&mut self, _: MailAddr, message: Never) -> BehaviorActed<Self> {
        match message {}
    }
}
struct Root;
#[behavior::behavior(addr = MailAddr, message = Never, births = {
    primary: Worker,
    backup: Worker,
})]
impl Root {
    fn receive(&mut self, _: MailAddr, message: Never) -> BehaviorActed<Self> {
        match message {}
    }
}
fn requires_primary(_: ChildRoute<Worker, RootChildrenPrimary>) {}
let routes = RootChildrenRoutes::new(1, 2);
requires_primary(routes.backup);

Named topology selectors accept only the child declared for that parent role, which lets an application builder remain entirely static:

ⓘ
use behavior::{Behavior, BehaviorActed, ChildRole, MailAddr, Never};
struct Worker;
#[behavior::behavior(addr = MailAddr, message = Never)]
impl Worker {
    fn receive(&mut self, _: MailAddr, message: Never) -> BehaviorActed<Self> {
        match message {}
    }
}
struct Query;
#[behavior::behavior(addr = MailAddr, message = Never)]
impl Query {
    fn receive(&mut self, _: MailAddr, message: Never) -> BehaviorActed<Self> {
        match message {}
    }
}
struct Root;
#[behavior::behavior(addr = MailAddr, message = Never, births = { workers: Worker })]
impl Root {
    fn receive(&mut self, _: MailAddr, message: Never) -> BehaviorActed<Self> {
        match message {}
    }
}
fn child<Parent, Role>(_: Role, _: Role::Child)
where
    Parent: Behavior,
    Role: ChildRole<Parent>,
{}
child::<Root, _>(RootChild::Workers, Query);

Generate the mechanical Behavior implementation for a normal inherent impl containing receive(&mut self, from, message) and, optionally, init(&mut self). Omitting init selects the behavior algebra’s empty initialization transition. The original impl and methods are preserved unchanged.