Skip to main content

Runner

Struct Runner 

Source
pub struct Runner<'a, M, E = Infallible>
where M: Machine<E>,
{ /* private fields */ }
Available on crate feature runtime only.
Expand description

Owns the current mode and feeds events into a Machine.

Runner is intentionally small. It is a convenience layer around the core contracts, not a framework.

Implementations§

Source§

impl<'a, M, E> Runner<'a, M, E>
where M: Machine<E>,

Source

pub fn new(machine: &'a M) -> Self

Creates a new runner starting at machine.initial_mode().

Examples found in repository?
examples/metrics.rs (line 137)
135fn main() {
136    let system = System;
137    let runner = Runner::new(&system);
138    let mut metrics = MetricsRunner::new(runner);
139
140    let events = [
141        Event::Start,
142        Event::Fault,
143        Event::Recover,
144        Event::Start,
145        Event::Fault,
146        Event::Recover,
147    ];
148
149    for event in &events {
150        println!("Event: {event:?}");
151        metrics.feed(event);
152    }
153
154    metrics.print_summary();
155
156    assert_eq!(metrics.transition_count, 6);
157    assert_eq!(metrics.commands_dispatched, 6);
158}
More examples
Hide additional examples
examples/runner.rs (line 76)
73fn main() {
74    let system = System;
75    let policy = ForwardOnly;
76    let mut runner = Runner::new(&system);
77
78    println!("Starting in {:?}", runner.mode());
79
80    // Allowed: Ready → Active
81    let commands = runner.feed_checked(&Event::Start, &policy);
82    println!("\nEvent: Start");
83    println!("  mode: {:?}, commands: {:?}", runner.mode(), commands);
84
85    // Denied: Active → Ready (policy blocks backward transitions)
86    let result = runner.feed_checked(&Event::Reset, &policy);
87    println!("\nEvent: Reset");
88    match &result {
89        Ok(cmds) => println!("  mode: {:?}, commands: {cmds:?}", runner.mode()),
90        Err(err) => println!("  denied: {err}"),
91    }
92    println!("  mode unchanged: {:?}", runner.mode());
93
94    // Allowed: Active → Safe
95    let commands = runner.feed_checked(&Event::Fault, &policy);
96    println!("\nEvent: Fault");
97    println!("  mode: {:?}, commands: {:?}", runner.mode(), commands);
98}
examples/channel_runtime.rs (line 100)
98fn main() {
99    let system = SensorSystem;
100    let mut runner = Runner::new(&system);
101
102    let (tx, rx) = mpsc::channel();
103
104    spawn_timer(tx.clone());
105    spawn_sensor(tx.clone());
106    spawn_fault_injector(tx);
107
108    println!("Starting in {:?}", runner.mode());
109
110    loop {
111        match rx.recv_timeout(Duration::from_millis(500)) {
112            Ok(event) => {
113                let before = runner.mode().clone();
114                runner.feed_and_dispatch(&event, |cmd| {
115                    println!("  dispatch: {cmd:?}");
116                });
117                let after = runner.mode();
118                println!("{before:?} + {event:?} => {after:?}");
119
120                if *after == Mode::Safe {
121                    println!("Reached Safe mode, shutting down.");
122                    break;
123                }
124            }
125            Err(mpsc::RecvTimeoutError::Timeout) => {
126                println!("No events for 500ms, shutting down.");
127                break;
128            }
129            Err(mpsc::RecvTimeoutError::Disconnected) => {
130                println!("All senders dropped, shutting down.");
131                break;
132            }
133        }
134    }
135
136    println!("Final mode: {:?}", runner.mode());
137}
examples/recovery_cycle.rs (line 65)
63fn main() {
64    let system = RecoverableSystem;
65    let mut runner = Runner::new(&system);
66    let max_recoveries: u32 = 3;
67    let mut recovery_count: u32 = 0;
68
69    println!("Starting in {:?}", runner.mode());
70
71    // Drive: Ready -> Active -> Safe -> Ready -> Active -> ...
72    loop {
73        // Ready -> Active
74        runner.feed_and_dispatch(&Event::Activate, |cmd| {
75            println!("  dispatch: {cmd:?}");
76        });
77        println!("Mode: {:?}", runner.mode());
78
79        // Active -> Safe (fault)
80        runner.feed_and_dispatch(&Event::Fault, |cmd| {
81            println!("  dispatch: {cmd:?}");
82        });
83        println!("Mode: {:?} (fault!)", runner.mode());
84
85        recovery_count = recovery_count.saturating_add(1);
86        println!("Recovery #{recovery_count}");
87
88        if recovery_count >= max_recoveries {
89            println!("Max recoveries ({max_recoveries}) reached, staying in Safe.");
90            break;
91        }
92
93        // Safe -> Ready (recover)
94        runner.feed_and_dispatch(&Event::RecoveryComplete, |cmd| {
95            println!("  dispatch: {cmd:?}");
96        });
97        println!("Mode: {:?} (recovered)", runner.mode());
98    }
99
100    println!(
101        "Final mode: {:?}, total recoveries: {recovery_count}",
102        runner.mode()
103    );
104    assert_eq!(runner.mode(), &Mode::Safe);
105    assert_eq!(recovery_count, max_recoveries);
106}
examples/replay.rs (line 77)
75fn main() {
76    let instrument = LabInstrument;
77    let mut runner = Runner::new(&instrument);
78
79    let events = [
80        Event::PowerOn,
81        Event::SensorReady,
82        Event::BeginMeasurement,
83        Event::DataCollected,
84        Event::DataCollected,
85        Event::AnomalyDetected,
86        Event::OperatorReset,
87        Event::BeginMeasurement,
88    ];
89
90    // Record every transition in the journal
91    let mut journal: InMemoryJournal<Mode, Event, Command> = InMemoryJournal::new();
92
93    println!("--- Recording transitions ---\n");
94    for event in events {
95        let from = runner.mode().clone();
96        let commands = runner.feed(&event);
97        let to = runner.mode().clone();
98
99        println!("  {from:?} + {event:?} => {to:?}");
100        if !commands.is_empty() {
101            println!("    commands: {commands:?}");
102        }
103
104        journal.record_step(&from, &to, &event, &commands);
105    }
106
107    println!("\n--- Journal: {} records ---\n", journal.len());
108    for record in journal.records() {
109        println!(
110            "  step {}: {:?} => {:?} (via {:?})",
111            record.step, record.from, record.to, record.event,
112        );
113    }
114
115    // Replay: feed the same events through a fresh machine and verify
116    println!("\n--- Replaying ---\n");
117    match journal.replay(&instrument, instrument.initial_mode()) {
118        Ok(final_mode) => {
119            println!("  replay verified: final mode = {final_mode:?}");
120            assert_eq!(final_mode, runner.mode().clone());
121        }
122        Err(err) => {
123            println!("  {err}");
124        }
125    }
126}
Source

pub const fn with_mode(machine: &'a M, mode: M::Mode) -> Self

Creates a runner with an explicit initial mode.

Source

pub const fn mode(&self) -> &M::Mode

Returns the current mode.

Examples found in repository?
examples/metrics.rs (line 78)
77    fn new(runner: Runner<'a, System>) -> Self {
78        let initial = runner.mode().clone();
79        let mut mode_entries = HashMap::new();
80        *mode_entries.entry(initial).or_insert(0) += 1;
81
82        Self {
83            runner,
84            transition_count: 0,
85            commands_dispatched: 0,
86            mode_entries,
87            mode_entered_at: Instant::now(),
88            mode_durations: HashMap::new(),
89        }
90    }
91
92    fn feed(&mut self, event: &Event) {
93        let before = self.runner.mode().clone();
94        let commands = self.runner.feed(event);
95
96        let after = self.runner.mode().clone();
97
98        self.commands_dispatched += commands.len() as u64;
99
100        if before != after {
101            self.transition_count += 1;
102
103            let elapsed = self.mode_entered_at.elapsed();
104            *self.mode_durations.entry(before).or_default() += elapsed;
105            self.mode_entered_at = Instant::now();
106
107            *self.mode_entries.entry(after.clone()).or_insert(0) += 1;
108            println!("  [metrics] transition to {after:?}");
109        }
110
111        for cmd in &commands {
112            println!("  [metrics] dispatch: {cmd:?}");
113        }
114    }
115
116    fn print_summary(&self) {
117        println!("\n=== Metrics Summary ===");
118        println!("Transitions:        {}", self.transition_count);
119        println!("Commands dispatched: {}", self.commands_dispatched);
120
121        println!("Mode entries:");
122        for (mode, count) in &self.mode_entries {
123            println!("  {mode:?}: {count}");
124        }
125
126        println!("Time in mode:");
127        for (mode, dur) in &self.mode_durations {
128            println!("  {mode:?}: {dur:?}");
129        }
130
131        println!("Current mode: {:?}", self.runner.mode());
132    }
More examples
Hide additional examples
examples/runner.rs (line 78)
73fn main() {
74    let system = System;
75    let policy = ForwardOnly;
76    let mut runner = Runner::new(&system);
77
78    println!("Starting in {:?}", runner.mode());
79
80    // Allowed: Ready → Active
81    let commands = runner.feed_checked(&Event::Start, &policy);
82    println!("\nEvent: Start");
83    println!("  mode: {:?}, commands: {:?}", runner.mode(), commands);
84
85    // Denied: Active → Ready (policy blocks backward transitions)
86    let result = runner.feed_checked(&Event::Reset, &policy);
87    println!("\nEvent: Reset");
88    match &result {
89        Ok(cmds) => println!("  mode: {:?}, commands: {cmds:?}", runner.mode()),
90        Err(err) => println!("  denied: {err}"),
91    }
92    println!("  mode unchanged: {:?}", runner.mode());
93
94    // Allowed: Active → Safe
95    let commands = runner.feed_checked(&Event::Fault, &policy);
96    println!("\nEvent: Fault");
97    println!("  mode: {:?}, commands: {:?}", runner.mode(), commands);
98}
examples/channel_runtime.rs (line 108)
98fn main() {
99    let system = SensorSystem;
100    let mut runner = Runner::new(&system);
101
102    let (tx, rx) = mpsc::channel();
103
104    spawn_timer(tx.clone());
105    spawn_sensor(tx.clone());
106    spawn_fault_injector(tx);
107
108    println!("Starting in {:?}", runner.mode());
109
110    loop {
111        match rx.recv_timeout(Duration::from_millis(500)) {
112            Ok(event) => {
113                let before = runner.mode().clone();
114                runner.feed_and_dispatch(&event, |cmd| {
115                    println!("  dispatch: {cmd:?}");
116                });
117                let after = runner.mode();
118                println!("{before:?} + {event:?} => {after:?}");
119
120                if *after == Mode::Safe {
121                    println!("Reached Safe mode, shutting down.");
122                    break;
123                }
124            }
125            Err(mpsc::RecvTimeoutError::Timeout) => {
126                println!("No events for 500ms, shutting down.");
127                break;
128            }
129            Err(mpsc::RecvTimeoutError::Disconnected) => {
130                println!("All senders dropped, shutting down.");
131                break;
132            }
133        }
134    }
135
136    println!("Final mode: {:?}", runner.mode());
137}
examples/recovery_cycle.rs (line 69)
63fn main() {
64    let system = RecoverableSystem;
65    let mut runner = Runner::new(&system);
66    let max_recoveries: u32 = 3;
67    let mut recovery_count: u32 = 0;
68
69    println!("Starting in {:?}", runner.mode());
70
71    // Drive: Ready -> Active -> Safe -> Ready -> Active -> ...
72    loop {
73        // Ready -> Active
74        runner.feed_and_dispatch(&Event::Activate, |cmd| {
75            println!("  dispatch: {cmd:?}");
76        });
77        println!("Mode: {:?}", runner.mode());
78
79        // Active -> Safe (fault)
80        runner.feed_and_dispatch(&Event::Fault, |cmd| {
81            println!("  dispatch: {cmd:?}");
82        });
83        println!("Mode: {:?} (fault!)", runner.mode());
84
85        recovery_count = recovery_count.saturating_add(1);
86        println!("Recovery #{recovery_count}");
87
88        if recovery_count >= max_recoveries {
89            println!("Max recoveries ({max_recoveries}) reached, staying in Safe.");
90            break;
91        }
92
93        // Safe -> Ready (recover)
94        runner.feed_and_dispatch(&Event::RecoveryComplete, |cmd| {
95            println!("  dispatch: {cmd:?}");
96        });
97        println!("Mode: {:?} (recovered)", runner.mode());
98    }
99
100    println!(
101        "Final mode: {:?}, total recoveries: {recovery_count}",
102        runner.mode()
103    );
104    assert_eq!(runner.mode(), &Mode::Safe);
105    assert_eq!(recovery_count, max_recoveries);
106}
examples/replay.rs (line 95)
75fn main() {
76    let instrument = LabInstrument;
77    let mut runner = Runner::new(&instrument);
78
79    let events = [
80        Event::PowerOn,
81        Event::SensorReady,
82        Event::BeginMeasurement,
83        Event::DataCollected,
84        Event::DataCollected,
85        Event::AnomalyDetected,
86        Event::OperatorReset,
87        Event::BeginMeasurement,
88    ];
89
90    // Record every transition in the journal
91    let mut journal: InMemoryJournal<Mode, Event, Command> = InMemoryJournal::new();
92
93    println!("--- Recording transitions ---\n");
94    for event in events {
95        let from = runner.mode().clone();
96        let commands = runner.feed(&event);
97        let to = runner.mode().clone();
98
99        println!("  {from:?} + {event:?} => {to:?}");
100        if !commands.is_empty() {
101            println!("    commands: {commands:?}");
102        }
103
104        journal.record_step(&from, &to, &event, &commands);
105    }
106
107    println!("\n--- Journal: {} records ---\n", journal.len());
108    for record in journal.records() {
109        println!(
110            "  step {}: {:?} => {:?} (via {:?})",
111            record.step, record.from, record.to, record.event,
112        );
113    }
114
115    // Replay: feed the same events through a fresh machine and verify
116    println!("\n--- Replaying ---\n");
117    match journal.replay(&instrument, instrument.initial_mode()) {
118        Ok(final_mode) => {
119            println!("  replay verified: final mode = {final_mode:?}");
120            assert_eq!(final_mode, runner.mode().clone());
121        }
122        Err(err) => {
123            println!("  {err}");
124        }
125    }
126}
Source

pub fn feed(&mut self, event: &M::Event) -> Vec<M::Command>

Feeds an event into the machine, applies any mode change, and returns emitted commands.

This does not enforce a Policy. Use Runner::feed_checked when you need explicit transition approval.

Examples found in repository?
examples/metrics.rs (line 94)
92    fn feed(&mut self, event: &Event) {
93        let before = self.runner.mode().clone();
94        let commands = self.runner.feed(event);
95
96        let after = self.runner.mode().clone();
97
98        self.commands_dispatched += commands.len() as u64;
99
100        if before != after {
101            self.transition_count += 1;
102
103            let elapsed = self.mode_entered_at.elapsed();
104            *self.mode_durations.entry(before).or_default() += elapsed;
105            self.mode_entered_at = Instant::now();
106
107            *self.mode_entries.entry(after.clone()).or_insert(0) += 1;
108            println!("  [metrics] transition to {after:?}");
109        }
110
111        for cmd in &commands {
112            println!("  [metrics] dispatch: {cmd:?}");
113        }
114    }
More examples
Hide additional examples
examples/replay.rs (line 96)
75fn main() {
76    let instrument = LabInstrument;
77    let mut runner = Runner::new(&instrument);
78
79    let events = [
80        Event::PowerOn,
81        Event::SensorReady,
82        Event::BeginMeasurement,
83        Event::DataCollected,
84        Event::DataCollected,
85        Event::AnomalyDetected,
86        Event::OperatorReset,
87        Event::BeginMeasurement,
88    ];
89
90    // Record every transition in the journal
91    let mut journal: InMemoryJournal<Mode, Event, Command> = InMemoryJournal::new();
92
93    println!("--- Recording transitions ---\n");
94    for event in events {
95        let from = runner.mode().clone();
96        let commands = runner.feed(&event);
97        let to = runner.mode().clone();
98
99        println!("  {from:?} + {event:?} => {to:?}");
100        if !commands.is_empty() {
101            println!("    commands: {commands:?}");
102        }
103
104        journal.record_step(&from, &to, &event, &commands);
105    }
106
107    println!("\n--- Journal: {} records ---\n", journal.len());
108    for record in journal.records() {
109        println!(
110            "  step {}: {:?} => {:?} (via {:?})",
111            record.step, record.from, record.to, record.event,
112        );
113    }
114
115    // Replay: feed the same events through a fresh machine and verify
116    println!("\n--- Replaying ---\n");
117    match journal.replay(&instrument, instrument.initial_mode()) {
118        Ok(final_mode) => {
119            println!("  replay verified: final mode = {final_mode:?}");
120            assert_eq!(final_mode, runner.mode().clone());
121        }
122        Err(err) => {
123            println!("  {err}");
124        }
125    }
126}
Source

pub fn feed_checked<P>( &mut self, event: &M::Event, policy: &P, ) -> Result<Vec<M::Command>, LifecycleError<M::Mode>>
where P: Policy<M::Mode>, M::Mode: Clone,

Like Runner::feed, but checks policy before applying a mode change.

§Errors

Returns LifecycleError::TransitionDenied if the policy rejects the transition.

Examples found in repository?
examples/runner.rs (line 81)
73fn main() {
74    let system = System;
75    let policy = ForwardOnly;
76    let mut runner = Runner::new(&system);
77
78    println!("Starting in {:?}", runner.mode());
79
80    // Allowed: Ready → Active
81    let commands = runner.feed_checked(&Event::Start, &policy);
82    println!("\nEvent: Start");
83    println!("  mode: {:?}, commands: {:?}", runner.mode(), commands);
84
85    // Denied: Active → Ready (policy blocks backward transitions)
86    let result = runner.feed_checked(&Event::Reset, &policy);
87    println!("\nEvent: Reset");
88    match &result {
89        Ok(cmds) => println!("  mode: {:?}, commands: {cmds:?}", runner.mode()),
90        Err(err) => println!("  denied: {err}"),
91    }
92    println!("  mode unchanged: {:?}", runner.mode());
93
94    // Allowed: Active → Safe
95    let commands = runner.feed_checked(&Event::Fault, &policy);
96    println!("\nEvent: Fault");
97    println!("  mode: {:?}, commands: {:?}", runner.mode(), commands);
98}
Source

pub fn feed_and_dispatch<F>(&mut self, event: &M::Event, dispatch: F)
where F: FnMut(M::Command),

Feeds an event and dispatches each command to dispatch.

This is a convenience helper for the common “execute commands immediately” pattern. Commands are dispatched in the order they were emitted.

Examples found in repository?
examples/channel_runtime.rs (lines 114-116)
98fn main() {
99    let system = SensorSystem;
100    let mut runner = Runner::new(&system);
101
102    let (tx, rx) = mpsc::channel();
103
104    spawn_timer(tx.clone());
105    spawn_sensor(tx.clone());
106    spawn_fault_injector(tx);
107
108    println!("Starting in {:?}", runner.mode());
109
110    loop {
111        match rx.recv_timeout(Duration::from_millis(500)) {
112            Ok(event) => {
113                let before = runner.mode().clone();
114                runner.feed_and_dispatch(&event, |cmd| {
115                    println!("  dispatch: {cmd:?}");
116                });
117                let after = runner.mode();
118                println!("{before:?} + {event:?} => {after:?}");
119
120                if *after == Mode::Safe {
121                    println!("Reached Safe mode, shutting down.");
122                    break;
123                }
124            }
125            Err(mpsc::RecvTimeoutError::Timeout) => {
126                println!("No events for 500ms, shutting down.");
127                break;
128            }
129            Err(mpsc::RecvTimeoutError::Disconnected) => {
130                println!("All senders dropped, shutting down.");
131                break;
132            }
133        }
134    }
135
136    println!("Final mode: {:?}", runner.mode());
137}
More examples
Hide additional examples
examples/recovery_cycle.rs (lines 74-76)
63fn main() {
64    let system = RecoverableSystem;
65    let mut runner = Runner::new(&system);
66    let max_recoveries: u32 = 3;
67    let mut recovery_count: u32 = 0;
68
69    println!("Starting in {:?}", runner.mode());
70
71    // Drive: Ready -> Active -> Safe -> Ready -> Active -> ...
72    loop {
73        // Ready -> Active
74        runner.feed_and_dispatch(&Event::Activate, |cmd| {
75            println!("  dispatch: {cmd:?}");
76        });
77        println!("Mode: {:?}", runner.mode());
78
79        // Active -> Safe (fault)
80        runner.feed_and_dispatch(&Event::Fault, |cmd| {
81            println!("  dispatch: {cmd:?}");
82        });
83        println!("Mode: {:?} (fault!)", runner.mode());
84
85        recovery_count = recovery_count.saturating_add(1);
86        println!("Recovery #{recovery_count}");
87
88        if recovery_count >= max_recoveries {
89            println!("Max recoveries ({max_recoveries}) reached, staying in Safe.");
90            break;
91        }
92
93        // Safe -> Ready (recover)
94        runner.feed_and_dispatch(&Event::RecoveryComplete, |cmd| {
95            println!("  dispatch: {cmd:?}");
96        });
97        println!("Mode: {:?} (recovered)", runner.mode());
98    }
99
100    println!(
101        "Final mode: {:?}, total recoveries: {recovery_count}",
102        runner.mode()
103    );
104    assert_eq!(runner.mode(), &Mode::Safe);
105    assert_eq!(recovery_count, max_recoveries);
106}
Source

pub fn feed_checked_and_dispatch<P, F>( &mut self, event: &M::Event, policy: &P, dispatch: F, ) -> Result<(), LifecycleError<M::Mode>>
where P: Policy<M::Mode>, M::Mode: Clone, F: FnMut(M::Command),

Like Runner::feed_and_dispatch, but checks policy before applying a mode change.

§Errors

Returns LifecycleError::TransitionDenied if the policy rejects the transition.

Trait Implementations§

Source§

impl<'a, M, E: Debug> Debug for Runner<'a, M, E>
where M: Machine<E> + Debug, M::Mode: Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<'a, M, E> Freeze for Runner<'a, M, E>
where <M as Machine<E>>::Mode: Freeze,

§

impl<'a, M, E> RefUnwindSafe for Runner<'a, M, E>
where <M as Machine<E>>::Mode: RefUnwindSafe, M: RefUnwindSafe,

§

impl<'a, M, E> Send for Runner<'a, M, E>
where <M as Machine<E>>::Mode: Send, M: Sync,

§

impl<'a, M, E> Sync for Runner<'a, M, E>
where <M as Machine<E>>::Mode: Sync, M: Sync,

§

impl<'a, M, E> Unpin for Runner<'a, M, E>
where <M as Machine<E>>::Mode: Unpin,

§

impl<'a, M, E> UnsafeUnpin for Runner<'a, M, E>
where <M as Machine<E>>::Mode: UnsafeUnpin,

§

impl<'a, M, E> UnwindSafe for Runner<'a, M, E>
where <M as Machine<E>>::Mode: UnwindSafe, M: RefUnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.