pub trait Machine<E = Infallible> {
type Mode;
type Event;
type Command;
// Required methods
fn initial_mode(&self) -> Self::Mode;
fn on_event(
&self,
mode: &Self::Mode,
event: &Self::Event,
) -> Decision<Self::Mode, Self::Command, E>;
// Provided methods
fn decide(
&self,
mode: &Self::Mode,
event: &Self::Event,
) -> Decision<Self::Mode, Self::Command, E> { ... }
fn step(
&self,
mode: Self::Mode,
event: &Self::Event,
) -> (Self::Mode, Vec<Self::Command>) { ... }
fn step_checked<P>(
&self,
mode: Self::Mode,
event: &Self::Event,
policy: &P,
) -> Result<(Self::Mode, Vec<Self::Command>), LifecycleError<Self::Mode>>
where P: Policy<Self::Mode> { ... }
}Expand description
A pure function from (mode, event) to Decision.
This is the central trait of the lifecycle engine. Implementations define how the system responds to events in each mode. The trait is deliberately minimal:
- No
selfmutation:on_eventtakes&self, ensuring the machine logic is pure and deterministic. - No time parameter: time composes via the runtime and the
timefeature. The base trait stays usable without it. - No I/O: the machine never performs side effects. It returns
a
Decisionthat the runtime interprets.
§Associated Types
Mode: the set of lifecycle phases (e.g., Ready, Active, Safe).Event: external stimuli the system can receive.Command: instructions for the runtime to execute.
§Type Parameter
E: domain errors that can occur during event processing. Defaults toInfallible.
§Examples
use ready_active_safe::prelude::*;
struct Echo;
impl Machine for Echo {
type Mode = ();
type Event = String;
type Command = String;
fn initial_mode(&self) {}
fn on_event(&self, _mode: &(), event: &String) -> Decision<(), String> {
Decision::stay().with_command(event.clone())
}
}
let echo = Echo;
let event = "hello".to_string();
let d = echo.on_event(&(), &event);
assert_eq!(d.commands(), &[event]);Required Associated Types§
Required Methods§
Sourcefn initial_mode(&self) -> Self::Mode
fn initial_mode(&self) -> Self::Mode
Returns the starting mode for this lifecycle.
Every lifecycle has a well-defined starting point. Declaring it here keeps the definition self-contained instead of requiring the caller to know the right value.
§Examples
use ready_active_safe::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq)]
enum Mode { Ready, Active }
struct System;
impl Machine for System {
type Mode = Mode;
type Event = ();
type Command = ();
fn initial_mode(&self) -> Mode { Mode::Ready }
fn on_event(&self, _: &Mode, _: &()) -> Decision<Mode, ()> {
stay()
}
}
let system = System;
assert_eq!(system.initial_mode(), Mode::Ready);Provided Methods§
Sourcefn decide(
&self,
mode: &Self::Mode,
event: &Self::Event,
) -> Decision<Self::Mode, Self::Command, E>
fn decide( &self, mode: &Self::Mode, event: &Self::Event, ) -> Decision<Self::Mode, Self::Command, E>
Alias for Machine::on_event.
This reads well in runtimes and tests.
Examples found in repository?
67fn main() {
68 let controller = Controller;
69 let mut mode = controller.initial_mode();
70
71 let events = [Event::Initialize, Event::Start, Event::Fault];
72
73 for event in &events {
74 let decision = controller.decide(&mode, event);
75 println!("{mode:?} + {event:?} => {decision}");
76
77 let (next_mode, commands) = decision.apply(mode);
78 if !commands.is_empty() {
79 println!(" commands: {commands:?}");
80 }
81
82 mode = next_mode;
83 }
84
85 assert_eq!(mode, Mode::Safe);
86}More examples
101fn main() {
102 let session = XrSession;
103 let mut mode = session.initial_mode();
104
105 let events = [
106 XrEvent::SessionStateChanged(SessionMode::Ready),
107 XrEvent::SessionStateChanged(SessionMode::Synchronized),
108 XrEvent::SessionStateChanged(SessionMode::Visible),
109 XrEvent::SessionStateChanged(SessionMode::Focused),
110 XrEvent::FrameReady,
111 XrEvent::FrameReady,
112 XrEvent::RequestExit,
113 ];
114
115 for event in &events {
116 let decision = session.decide(&mode, event);
117 println!("{mode:?} + {event:?} => {decision}");
118
119 let (next_mode, commands) = decision.apply(mode);
120 if !commands.is_empty() {
121 println!(" commands: {commands:?}");
122 }
123
124 mode = next_mode;
125 }
126
127 assert_eq!(mode, SessionMode::Stopping);
128}97fn main() -> Result<(), LifecycleError<Mode>> {
98 let controller = SafetyController;
99 let policy = LifecyclePolicy;
100 let mut mode = controller.initial_mode();
101
102 let events = [
103 Event::Activate,
104 Event::TransientFault,
105 Event::CriticalFault,
106 Event::RecoveryApproved,
107 Event::DiagnosticsComplete,
108 Event::Activate,
109 Event::Shutdown,
110 ];
111
112 for event in &events {
113 let decision = controller.decide(&mode, event);
114 println!("{mode:?} + {event:?} => {decision}");
115
116 match decision.apply_checked(mode, &policy) {
117 Ok((next_mode, commands)) => {
118 if !commands.is_empty() {
119 println!(" commands: {commands:?}");
120 }
121 mode = next_mode;
122 }
123 Err(LifecycleError::TransitionDenied { from, to, reason }) => {
124 println!(" denied: {from:?} -> {to:?} ({reason})");
125 mode = from;
126 }
127 Err(err) => return Err(err),
128 }
129 }
130
131 assert!(!policy.is_allowed(&Mode::Active, &Mode::Ready));
132 assert!(!policy.is_allowed(&Mode::Safe, &Mode::Active));
133 assert_eq!(mode, Mode::Safe);
134
135 Ok(())
136}Sourcefn step(
&self,
mode: Self::Mode,
event: &Self::Event,
) -> (Self::Mode, Vec<Self::Command>)
fn step( &self, mode: Self::Mode, event: &Self::Event, ) -> (Self::Mode, Vec<Self::Command>)
Returns the next mode and commands for an owned current mode.
This is a convenience for runtimes that own the mode value.
It is equivalent to calling decide and then Decision::apply.
Sourcefn step_checked<P>(
&self,
mode: Self::Mode,
event: &Self::Event,
policy: &P,
) -> Result<(Self::Mode, Vec<Self::Command>), LifecycleError<Self::Mode>>
fn step_checked<P>( &self, mode: Self::Mode, event: &Self::Event, policy: &P, ) -> Result<(Self::Mode, Vec<Self::Command>), LifecycleError<Self::Mode>>
Returns the next mode and commands after checking a policy.
This is a convenience for runtimes that want policy checks to be
explicit and centralized. It is equivalent to calling decide and then
Decision::apply_checked.
§Errors
Returns LifecycleError::TransitionDenied if the policy rejects
the requested mode change.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.