Skip to main content

behavior

Attribute Macro behavior 

#[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 a module-private ActorSends; sends = pub { lane: Product } exports it for a public actor. Its lane selectors, settlement product, and fluent trait have the same visibility. Each field contributes its logical-host protocols in declared order, including duplicates, through LogicalDeliveryProtocols. The product also derives structural SendEffects, SendsFor, SendSettlements, and InterpretSends implementations. The settlement product keeps the same semantic field names and one runtime-independent type. The macro 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 closed child algebra. One child remains its direct concrete type; multiple alternatives form ChildChoice in declaration order. The macro also generates one nominal role per declaration and an ActorChild namespace for those roles. Every role implements both ChildRole for its authored parent and ChildOccurrence for sealed resolution against that parent or a topology-transparent wrapper. Creation remains an authored Creations or Children value and is never performed by the macro.

Invalid receivers are rejected at compile time. A birth-owning generated actor must also select the exact creation-settlement disposition; there is no implicit discard or generated no-op receiver.

ⓘ
struct Child;
#[behavior::behavior(addr = behavior::MailAddr, message = behavior::Never)]
impl Child {
    fn receive(
        &mut self,
        _: behavior::MailAddr,
        message: behavior::Never,
    ) -> behavior::BehaviorActed<Self> {
        match message {}
    }
}
struct MissingCreationSettlementDisposition;
#[behavior::behavior(
    addr = behavior::MailAddr,
    message = behavior::Never,
    births = { child: Child },
)]
impl MissingCreationSettlementDisposition {
    fn receive(
        &mut self,
        _: behavior::MailAddr,
        message: behavior::Never,
    ) -> behavior::BehaviorActed<Self> {
        match message {}
    }
}
ⓘ

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

Missing receive methods are rejected by the macro itself:

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

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

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

Undeclared send lanes have no selector and cannot be emitted:

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

Generated lane methods accept only inputs supported by that lane:

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

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

ⓘ
struct Declared;
#[behavior::behavior(addr = behavior::MailAddr, message = behavior::Never)]
impl Declared {
    fn receive(&mut self, _: behavior::MailAddr, message: behavior::Never) -> behavior::BehaviorActed<Self> {
        match message {}
    }
}
struct Other;
#[behavior::behavior(addr = behavior::MailAddr, message = behavior::Never)]
impl Other {
    fn receive(&mut self, _: behavior::MailAddr, message: behavior::Never) -> behavior::BehaviorActed<Self> {
        match message {}
    }
}
struct Root { creations: behavior::CreationSequence }
#[behavior::behavior(
    addr = behavior::MailAddr,
    message = (),
    births = { declared: Declared },
    creation_settlements = retain_for_retirement,
)]
impl Root {
    fn receive(&mut self, _: behavior::MailAddr, _: ()) -> behavior::BehaviorActed<Self> {
        let id = self.creations.issue().expect("fixture creation ID");
        Ok(behavior::Actions::create(behavior::Creations::one(behavior::CreateChild::birth(id, Other))))
    }
}

Every generated send lane remains a separate interpreter obligation:

ⓘ
struct Root;
type AuditProtocol = behavior::MessageProtocol<behavior::MailAddr, u8>;
type MetricsProtocol = behavior::MessageProtocol<behavior::MailAddr, u16>;
#[behavior::behavior(addr = behavior::MailAddr, message = (), sends = {
    audit: Vec<behavior::Delivery<AuditProtocol>>,
    metrics: Vec<behavior::Delivery<MetricsProtocol>>,
})]
impl Root {
    fn receive(&mut self, _: behavior::MailAddr, _: ()) -> behavior::BehaviorActed<Self> {
        Ok(behavior::Actions::cont())
    }
}
struct Incomplete;
impl<RootEvent, Path> behavior::InterpretItem<behavior::Delivery<AuditProtocol>, RootEvent, Path> for Incomplete {
    fn interpret_item<'a>(&'a mut self, input: &'a mut Option<behavior::Delivery<AuditProtocol>>, received: &'a mut Option<<behavior::Delivery<AuditProtocol> as behavior::ActionItem>::Reply>) -> impl core::future::Future<Output = ()> + Send + 'a where behavior::Delivery<AuditProtocol>: 'a {
        async move {
            if received.is_some() { return; }
            let Some(delivery) = input.take() else { return; };
            drop(delivery);
            *received = Some(behavior::ItemSettlement::Accepted(()));
        }
    }
}
fn require_complete()
where
    RootSends: behavior::InterpretSends<Incomplete, behavior::User<behavior::MailAddr, ()>, behavior::Here>,
{}

Generated child products likewise require one EstablishChild implementation for every declared alternative. DispatchBirth owns the compile-denial example so this crate overview does not duplicate it.

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

ⓘ
struct Worker;
#[behavior::behavior(addr = behavior::MailAddr, message = ())]
impl Worker {
    fn receive(&mut self, _: behavior::MailAddr, _: ()) -> behavior::BehaviorActed<Self> {
        Ok(behavior::Actions::cont())
    }
}
struct Root;
#[behavior::behavior(addr = behavior::MailAddr, message = behavior::Never, births = {
    primary: Worker,
    backup: Worker,
}, creation_settlements = retain_for_retirement)]
impl Root {
    fn receive(&mut self, _: behavior::MailAddr, message: behavior::Never) -> behavior::BehaviorActed<Self> {
        match message {}
    }
}
fn requires_primary(
    _: behavior::ChildDelivery<<Worker as behavior::Behavior>::Protocol, RootChildrenPrimary>,
) {}
let mut sequence = behavior::CreationSequence::new();
let id = sequence.issue().expect("fixture creation ID");
let backup = behavior::ChildDelivery::<<Worker as behavior::Behavior>::Protocol, RootChildrenBackup>::after(id, ());
requires_primary(backup);

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

ⓘ
struct Worker;
#[behavior::behavior(addr = behavior::MailAddr, message = behavior::Never)]
impl Worker {
    fn receive(&mut self, _: behavior::MailAddr, message: behavior::Never) -> behavior::BehaviorActed<Self> {
        match message {}
    }
}
struct Query;
#[behavior::behavior(addr = behavior::MailAddr, message = behavior::Never)]
impl Query {
    fn receive(&mut self, _: behavior::MailAddr, message: behavior::Never) -> behavior::BehaviorActed<Self> {
        match message {}
    }
}
struct Root;
#[behavior::behavior(
    addr = behavior::MailAddr,
    message = behavior::Never,
    births = { workers: Worker },
    creation_settlements = retain_for_retirement,
)]
impl Root {
    fn receive(&mut self, _: behavior::MailAddr, message: behavior::Never) -> behavior::BehaviorActed<Self> {
        match message {}
    }
}
fn child<Parent, Role>(_: Role, _: Role::Child)
where
    Parent: behavior::Behavior,
    Role: behavior::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.