From 8391c61d6b64adfd2b2f49caa99b0957b36224e4 Mon Sep 17 00:00:00 2001 From: Oliver With Date: Tue, 26 Aug 2025 09:43:55 +0200 Subject: [PATCH 1/2] [wip] docu, fine tuning --- .continue/rules/project-specific-rules.md | 4 +- .vscode/launch.json | 45 +++++++ README.md | 48 ++++--- assistants/CLAUDE.md | 13 +- src/machines/lathe.rs | 91 ++++++++++--- src/machines/mill.rs | 26 ++++ src/machines/shared.rs | 157 ++++++++++++++++++++-- 7 files changed, 329 insertions(+), 55 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.continue/rules/project-specific-rules.md b/.continue/rules/project-specific-rules.md index bf7c1db..eeb10d8 100644 --- a/.continue/rules/project-specific-rules.md +++ b/.continue/rules/project-specific-rules.md @@ -2,4 +2,6 @@ {} --- -Always read and follow .clinerules and CLAUDE.md \ No newline at end of file +Always read and follow .clinerules and CLAUDE.md + +Don't create new files, rather edit the existing files diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3f42f83 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'fsm'", + "cargo": { + "args": [ + "run", + "--bin=fsm", + "--package=fsm", + ], + "filter": { + "name": "fsm", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'fsm'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=fsm", + "--package=fsm" + ], + "filter": { + "name": "fsm", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/README.md b/README.md index e4fa0f3..1c248c8 100644 --- a/README.md +++ b/README.md @@ -3,26 +3,38 @@ **Features** - Type Save FSM (type state pattern) - FSM run in their own thread -- communication via message queues -- State Transition and message handling boiler plate managed by `fsm!` macro +- Communication via bidirectional message queues +- State transition and message handling boiler plate managed by `fsm!` macro +One such FSM is a simplistic lathe: +```mermaid +stateDiagram-v2 + state "Off" as off + state "Spindle Spinning" as spinning + state "Feed" as feeding + state "Emergency Stop" as notaus -One such FSM is a simplistic lathe + [*] --> off + off --> spinning + spinning --> feeding + spinning --> off + feeding --> spinning + off --> notaus + spinning --> notaus + feeding --> notaus + notaus --> off : Acknowledge +``` -```plantuml -@startuml -state "Off" as off -state "Spindle Spinning" as spinning -state "Feed" as feeding -state "Emergency Stop" as notaus +Another FSM is a Mill with different state transitions but based on the same mechanics +```mermaid +stateDiagram-v2 + state "Off" as off + state "Spinning" as spinning + state "Moving" as moving -off --> spinning -spinning --> feeding -spinning --> off -feeding --> spinning -off --> notaus -spinning --> notaus -feeding --> notaus -notaus --> off: Acknowledge -@enduml + [*] --> off + off --> spinning : StartSpinning(revs) + spinning --> off : StopSpinning + spinning --> moving : Move(linear_move) + moving --> spinning : StopMoving ``` diff --git a/assistants/CLAUDE.md b/assistants/CLAUDE.md index b34675e..d5e96c6 100644 --- a/assistants/CLAUDE.md +++ b/assistants/CLAUDE.md @@ -1,13 +1,16 @@ @README.md -## Documentation - +## Documentatio - When creating documentation, don't list dependencies. - Don't mention mod.rs in drawings and other documentation. But you may read mod.rs to gain information about the structure +### Doc-Strings +- Focus on the "why" something is needed, don't explain "how" something works +- Be brief +- Ask, if not sure what to write -## Do +## Do - **Do**: Ask before removing rust warnings because I do this myself. Give me an option to skip this step. - **Do**: Doc Strings: - focus on the intent of the code, not on the implementation. If unclear, ask. @@ -18,6 +21,4 @@ ## Don't - **Don't** Comment things in unit tests - **Don't** run the tests and start fixing things on your own. Ask first - - -- **Do not**: Write down what you just did at the end of a task +- **Don't**: Write down what you just did at the end of a task diff --git a/src/machines/lathe.rs b/src/machines/lathe.rs index 6faec08..acb49f6 100644 --- a/src/machines/lathe.rs +++ b/src/machines/lathe.rs @@ -1,7 +1,18 @@ +//! Lathe FSM implementation using hand-coded approach +//! +//! This module demonstrates a fully manual implementation of the finite state machine pattern +//! without using the `fsm!` macro. All boilerplate code is written explicitly to show the +//! underlying mechanics that the macro would otherwise generate automatically. +//! +//! Compare this with `mill.rs` which uses the `fsm!` macro to generate equivalent functionality +//! with significantly less code. This manual approach provides full control and transparency +//! over the implementation details at the cost of more verbose code. + use std::marker::PhantomData; use super::shared::{MachineController, StateHandler}; +/// Commands that are sent to the lathe FSM #[derive(Debug)] pub enum LatheCommand { StartSpinning(u32), @@ -12,6 +23,7 @@ pub enum LatheCommand { Acknowledge, } +/// Responses returned by the lathe FSM #[derive(Debug, Clone, PartialEq)] pub enum LatheResponse { Status { @@ -23,6 +35,7 @@ pub enum LatheResponse { }, } +/// Lathe states - zero-sized types for compile-time state tracking #[derive(Debug)] pub struct Off; #[derive(Debug)] @@ -32,87 +45,110 @@ pub struct Feeding; #[derive(Debug)] pub struct Notaus; +/// Business data for the lathe FSM #[derive(Default, Debug)] pub struct LatheData { revs: u32, feed: u32, } +/// Main FSM struct using type-state pattern +/// +/// This is manually implemented to show the structure that the `fsm!` macro +/// would generate automatically. The generic `State` parameter ensures +/// compile-time verification of valid state transitions. +/// The actual data needed for the operation is passed around as a reference to a boxed value. +/// Therefore, no extra stack or heap allocations are needed. #[derive(Debug)] pub struct Lathe { state: PhantomData, - business_data: Box, + lathe_data: Box, } +/// Generic implementations available for all states impl Lathe { + /// Creates a new lathe FSM in the Off state pub fn new(data: Box) -> Lathe { Lathe { state: PhantomData, - business_data: data, + lathe_data: data, } } + /// Emergency stop transition available from any state pub fn notaus(self) -> Lathe { Lathe { state: PhantomData, - business_data: self.business_data, + lathe_data: self.lathe_data, } } + /// Debug helper to print current state and data pub fn print(&self) { - println!("State {:?}, Data {:#?}", self.state, self.business_data) + println!("State {:?}, Data {:#?}", self.state, self.lathe_data) } } +/// State-specific transitions for Off state +/// +/// These methods are manually implemented for each state, defining valid transitions. +/// The `fsm!` macro would generate equivalent methods automatically. impl Lathe { pub fn start_spinning(mut self, revs: u32) -> Lathe { - self.business_data.revs = revs; + self.lathe_data.revs = revs; Lathe { state: PhantomData, - business_data: self.business_data, + lathe_data: self.lathe_data, } } } +/// State-specific transitions for Spinning state impl Lathe { pub fn feed(mut self, feed: u32) -> Lathe { - self.business_data.feed = feed; + self.lathe_data.feed = feed; Lathe { state: PhantomData, - business_data: self.business_data, + lathe_data: self.lathe_data, } } pub fn off(mut self) -> Lathe { - self.business_data = Default::default(); + self.lathe_data = Default::default(); Lathe { state: PhantomData, - business_data: self.business_data, + lathe_data: self.lathe_data, } } } +/// State-specific transitions for Feeding state impl Lathe { pub fn stop_feed(mut self) -> Lathe { - self.business_data.feed = 0; + self.lathe_data.feed = 0; Lathe { state: PhantomData, - business_data: self.business_data, + lathe_data: self.lathe_data, } } } +/// State-specific transitions for Notaus (emergency stop) state impl Lathe { pub fn acknowledge(mut self) -> Lathe { - self.business_data = Default::default(); + self.lathe_data = Default::default(); Lathe { state: PhantomData, - business_data: self.business_data, + lathe_data: self.lathe_data, } } } +/// Runtime wrapper enum for handling dynamic state switching +/// +/// This enum is manually implemented to allow runtime state management. +/// The `fsm!` macro generates equivalent wrapper enums automatically. #[derive(Debug)] pub enum LatheWrapper { Off(Lathe), @@ -121,11 +157,13 @@ pub enum LatheWrapper { Notaus(Lathe), } +/// Wrapper implementation for runtime state management impl LatheWrapper { pub fn new(lathe_data: Box) -> Self { LatheWrapper::Off(Lathe::::new(lathe_data)) } + /// Delegates command handling to the appropriate state-specific handler pub fn handle_cmd(self, cmd: LatheCommand) -> (LatheWrapper, LatheResponse) { match self { LatheWrapper::Off(lathe) => lathe.handle_cmd(cmd), @@ -156,7 +194,13 @@ impl LatheController { } } -// State-specific handlers +/// Manual implementation of state-specific command handlers +/// +/// Each state must implement the StateHandler trait, defining which commands +/// are valid and how they transform the state. The `fsm!` macro generates these +/// implementations automatically based on the declarative state machine definition. +/// +/// Command handler for Off state impl StateHandler for Lathe { fn handle_cmd(self, cmd: LatheCommand) -> (LatheWrapper, LatheResponse) { match cmd { @@ -185,6 +229,7 @@ impl StateHandler for Lathe { } } +/// Command handler for Spinning state impl StateHandler for Lathe { fn handle_cmd(self, cmd: LatheCommand) -> (LatheWrapper, LatheResponse) { match cmd { @@ -220,6 +265,7 @@ impl StateHandler for Lathe } } +/// Command handler for Feeding state impl StateHandler for Lathe { fn handle_cmd(self, cmd: LatheCommand) -> (LatheWrapper, LatheResponse) { match cmd { @@ -248,6 +294,7 @@ impl StateHandler for Lathe } } +/// Command handler for Notaus (emergency stop) state impl StateHandler for Lathe { fn handle_cmd(self, cmd: LatheCommand) -> (LatheWrapper, LatheResponse) { match cmd { @@ -283,7 +330,7 @@ mod tests { let spinning_lathe = lathe.start_spinning(1500); - assert_eq!(spinning_lathe.business_data.revs, 1500); + assert_eq!(spinning_lathe.lathe_data.revs, 1500); } #[test] @@ -293,8 +340,8 @@ mod tests { let feeding_lathe = lathe.feed(250); - assert_eq!(feeding_lathe.business_data.feed, 250); - assert_eq!(feeding_lathe.business_data.revs, 1000); + assert_eq!(feeding_lathe.lathe_data.feed, 250); + assert_eq!(feeding_lathe.lathe_data.revs, 1000); } #[test] @@ -304,8 +351,8 @@ mod tests { let spinning_lathe = lathe.stop_feed(); - assert_eq!(spinning_lathe.business_data.feed, 0); - assert_eq!(spinning_lathe.business_data.revs, 1200); + assert_eq!(spinning_lathe.lathe_data.feed, 0); + assert_eq!(spinning_lathe.lathe_data.revs, 1200); } #[test] @@ -316,8 +363,8 @@ mod tests { let notaus_lathe = lathe.notaus(); let off_lathe = notaus_lathe.acknowledge(); - assert_eq!(off_lathe.business_data.revs, 0); - assert_eq!(off_lathe.business_data.feed, 0); + assert_eq!(off_lathe.lathe_data.revs, 0); + assert_eq!(off_lathe.lathe_data.feed, 0); } } diff --git a/src/machines/mill.rs b/src/machines/mill.rs index 84f4e3a..6791a8b 100644 --- a/src/machines/mill.rs +++ b/src/machines/mill.rs @@ -1,7 +1,18 @@ +//! Mill FSM implementation using the `fsm!` macro +//! +//! This module demonstrates how to leverage the `fsm!` macro from `shared.rs` to generate +//! boilerplate code for finite state machine implementation. The macro automatically creates +//! state transition methods, wrapper enums, and command handling logic based on the declarative +//! state machine definition. +//! +//! Compare this with `lathe.rs` which implements the same FSM pattern manually to understand +//! the code generation benefits of the macro approach. + use super::shared::{FSM, MachineController, StateHandler, fsm}; use std::marker::PhantomData; +/// Mill states - these are zero-sized types used for compile-time state tracking #[derive(Debug)] pub struct Off; #[derive(Debug)] @@ -11,12 +22,14 @@ pub struct Moving; #[derive(Debug)] pub struct Notaus; +/// Business data for the mill FSM #[derive(Default, Debug)] pub struct MillData { revs: u32, linear_move: i32, } +/// Commands that can be sent to the mill FSM #[derive(Debug)] pub enum MillCommand { StartSpinning(u32), @@ -25,6 +38,7 @@ pub enum MillCommand { StopMoving, } +/// Responses returned by the mill FSM #[derive(Debug, Clone, PartialEq)] pub enum MillResponse { Status { @@ -36,6 +50,18 @@ pub enum MillResponse { }, } +// FSM definition using the `fsm!` macro +// +// This macro call generates all the boilerplate code that would otherwise need to be +// written manually. +// It creates: +// - State transition methods for each FSM struct +// - A wrapper enum to handle runtime state switching +// - Command handling implementations for each state +// - Controller type alias and factory method +// +// The declarative syntax makes the state machine structure clear and reduces +// the chance of implementation errors compared to manual coding. fsm! { StartState: Off, MachineData: MillData, diff --git a/src/machines/shared.rs b/src/machines/shared.rs index 445d1c3..1683511 100644 --- a/src/machines/shared.rs +++ b/src/machines/shared.rs @@ -1,12 +1,34 @@ +/// Finite State Machine (FSM) implementation +/// This module provides a generic FSM framework +/// +/// The FSM is implemented using a type-state pattern where the state is represented by a generic parameter. +/// This allows for compile-time checking of valid state transitions. use std::marker::PhantomData; use std::sync::mpsc; use std::thread::{self, JoinHandle}; +/// Represents a Finite State Machine with a specific state and data. +/// +/// # Type Parameters +/// * `State` - The current state of the FSM +/// * `FsmData` - The data associated with the FSM pub struct FSM { pub state: PhantomData, pub data: Box, } +/// Macro for defining a Finite State Machine. +/// +/// This macro generates the necessary implementations for the FSM based on the provided states and transitions. +/// +/// # Parameters +/// * `StartState` - The initial state of the FSM +/// * `MachineData` - The type of data associated with the FSM +/// * `MachineCommand` - The type of commands that are be sent to the FSM +/// * `MachineResponse` - The type of responses that are returned by the FSM +/// * `StateHandlerTrait` - The trait that defines the interface for handling commands +/// * `Controller` - The type of controller for the FSM +/// * The rest of the parameters define the states and transitions of the FSM macro_rules! fsm { ( StartState: $start_state:ident, @@ -25,6 +47,13 @@ macro_rules! fsm { )* ) => { impl <$start_state, $data> FSM<$start_state, $data>{ + /// Creates a new FSM with the given data. + /// + /// # Arguments + /// * `data` - The data to associate with the FSM + /// + /// # Returns + /// A new FSM instance pub fn new(data: Box<$data>) -> FSM<$start_state, $data> { FSM{ state: PhantomData, @@ -37,6 +66,7 @@ macro_rules! fsm { impl FSM where $data : std::fmt::Debug { + /// Prints the current state and data of the FSM. pub fn print(&self) { println!("State {:?}, Data {:#?}", self.state, self.data) } @@ -45,6 +75,14 @@ macro_rules! fsm { $( impl FSM<$from_state, $data> { $( + /// Handles a command and transitions to a new state. + /// + /// # Arguments + /// * `self` - The current FSM instance + /// * The parameters for the command, if any + /// + /// # Returns + /// A new FSM instance with the updated state pub fn $method(mut $self $(, $($param: $param_type),+)?) -> FSM<$to_state, $data> { $( $($body)* @@ -59,7 +97,10 @@ macro_rules! fsm { )* - pub enum FsmWrapper { + /// Wrapper enum for the FSM states. + /// + /// This enum is used to represent the different states of the FSM in a type-safe way. +pub enum FsmWrapper { $( $from_state(FSM<$from_state, $data>), )* @@ -67,10 +108,25 @@ macro_rules! fsm { impl FsmWrapper{ + /// Creates a new FSM wrapper with the given data. + /// + /// # Arguments + /// * `machine_data` - The data to associate with the FSM + /// + /// # Returns + /// A new FSM wrapper instance pub fn new(machine_data: Box<$data>) -> Self { FsmWrapper::$start_state(FSM::<$start_state, $data>::new(machine_data)) } + /// Handles a command and returns the new state and response. + /// + /// # Arguments + /// * `self` - The current FSM wrapper instance + /// * `cmd` - The command to handle + /// + /// # Returns + /// A tuple containing the new FSM wrapper instance and the response pub fn handle_cmd(self, cmd: $command_type) -> (FsmWrapper, $response){ match self{ $( @@ -81,20 +137,43 @@ macro_rules! fsm { } impl From> for FsmWrapper { + /// Converts the given data into an FSM wrapper. + /// + /// # Arguments + /// * `lathe_data` - The data to convert + /// + /// # Returns + /// An FSM wrapper instance fn from(lathe_data: Box<$data>) -> Self { FsmWrapper::Off(FSM::<$start_state, $data>::new(lathe_data)) } } impl $state_handler<$command_type, $response, FsmWrapper> for FsmWrapper { + /// Handles a command and returns the new state and response. + /// + /// # Arguments + /// * `self` - The current FSM wrapper instance + /// * `cmd` - The command to handle + /// + /// # Returns + /// A tuple containing the new FSM wrapper instance and the response fn handle_cmd(self, cmd: $command_type) -> (FsmWrapper, $response) { self.handle_cmd(cmd) } } - pub type FsmController = $controller<$command_type, $response>; + /// Type alias for the FSM controller. +pub type FsmController = $controller<$command_type, $response>; impl FsmController { + /// Creates a new FSM controller with the given data. + /// + /// # Arguments + /// * `lathe_data` - The data to associate with the FSM + /// + /// # Returns + /// A new FSM controller instance pub fn create(lathe_data: Box<$data>) -> Self { $controller::new::, FsmWrapper>(lathe_data) } @@ -103,6 +182,14 @@ macro_rules! fsm { $( impl $state_handler<$command_type, $response, FsmWrapper> for FSM<$from_state, $data>{ + /// Handles a command and returns the new state and response. + /// + /// # Arguments + /// * `self` - The current FSM instance + /// * `cmd` - The command to handle + /// + /// # Returns + /// A tuple containing the new FSM wrapper instance and the response fn handle_cmd(self, cmd: $command_type) -> (FsmWrapper, $response){ match cmd { $( @@ -135,10 +222,29 @@ macro_rules! fsm { pub(in crate::machines) use fsm; +/// Trait for handling commands in the FSM. +/// +/// # Type Parameters +/// * `Command` - The type of commands that can be handled +/// * `Response` - The type of responses that can be returned +/// * `FsmWrapper` - The type of FSM wrapper pub trait StateHandler { + /// Handles a command and returns the new state and response. + /// + /// # Arguments + /// * `self` - The current FSM instance + /// * `cmd` - The command to handle + /// + /// # Returns + /// A tuple containing the new FSM wrapper instance and the response fn handle_cmd(self, cmd: Command) -> (FsmWrapper, Response); } +/// Controller for managing an FSM in a separate thread. +/// +/// # Type Parameters +/// * `Command` - The type of commands that can be sent to the FSM +/// * `Response` - The type of responses that can be returned by the FSM pub struct MachineController where Command: Send + 'static, @@ -154,6 +260,13 @@ where Command: Send + 'static, Response: Send + 'static, { + /// Creates a new FSM controller with the given data. + /// + /// # Arguments + /// * `machine_data` - The data to associate with the FSM + /// + /// # Returns + /// A new FSM controller instance pub fn new(machine_data: MachineData) -> Self where FsmWrapper: @@ -161,13 +274,10 @@ where { let fsm_wrapper = FsmWrapper::from(machine_data); - let (cmd_tx, cmd_rx): (mpsc::Sender, mpsc::Receiver) = - std::sync::mpsc::channel(); - let (response_tx, response_rx): (mpsc::Sender, mpsc::Receiver) = - std::sync::mpsc::channel(); + let (cmd_tx, cmd_rx) = std::sync::mpsc::channel(); + let (response_tx, response_rx) = std::sync::mpsc::channel(); + let machine_thread = MachineThread::new(cmd_rx, response_tx, fsm_wrapper); - let machine_thread: MachineThread = - MachineThread::new(cmd_rx, response_tx, fsm_wrapper); let thread_handle = thread::spawn(move || { machine_thread.run(); }); @@ -179,10 +289,21 @@ where } } + /// Sends a command to the FSM. + /// + /// # Arguments + /// * `cmd` - The command to send + /// + /// # Returns + /// `Ok(())` if the command was sent successfully, `Err` otherwise pub fn send_command(&self, cmd: Command) -> Result<(), &'static str> { self.cmd_tx.send(cmd).map_err(|_| "Failed to send command") } + /// Checks for any responses from the FSM. + /// + /// # Returns + /// A vector of responses pub fn check_responses(&self) -> Vec { let mut responses = Vec::new(); while let Ok(response) = self.response_rx.try_recv() { @@ -191,6 +312,10 @@ where responses } + /// Shuts down the FSM controller. + /// + /// # Returns + /// `Ok(())` if the controller was shut down successfully, `Err` otherwise pub fn shutdown(self) -> Result<(), Box> { drop(self.cmd_tx); @@ -201,6 +326,12 @@ where } } +/// Thread for running the FSM. +/// +/// # Type Parameters +/// * `Command` - The type of commands that can be sent to the FSM +/// * `Response` - The type of responses that can be returned by the FSM +/// * `FsmWrapper` - The type of FSM wrapper struct MachineThread { cmd_rx: mpsc::Receiver, response_tx: mpsc::Sender, @@ -211,6 +342,15 @@ impl MachineThread where FsmWrapper: StateHandler, { + /// Creates a new FSM thread. + /// + /// # Arguments + /// * `cmd_rx` - The receiver for commands + /// * `response_tx` - The sender for responses + /// * `fsm_wrapper` - The FSM wrapper + /// + /// # Returns + /// A new FSM thread instance fn new( cmd_rx: mpsc::Receiver, response_tx: mpsc::Sender, @@ -223,6 +363,7 @@ where } } + /// Runs the FSM thread. fn run(mut self) { while let Ok(cmd) = self.cmd_rx.recv() { let (new_actor, response) = self.fsm_wrapper.handle_cmd(cmd); From 0a85222b7f16978b390dd8c6bad27bbd740a19b3 Mon Sep 17 00:00:00 2001 From: Oliver With Date: Tue, 26 Aug 2025 10:05:15 +0200 Subject: [PATCH 2/2] Documentation for Github --- .github/workflows/rust-ci.yml | 27 +++++++++++++++++++++++++++ src/machines/mill.rs | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 9a25f99..fa7adca 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -45,3 +45,30 @@ jobs: with: name: fsm_linux path: target/release/fsm + + docs: + runs-on: ubuntu-latest + needs: build + if: github.ref != 'refs/heads/gh-pages' # Skip if triggered from gh-pages + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Generate documentation + run: cargo doc --workspace --no-deps + + - name: whereis + run: | + ls -la ./target/doc + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./target/doc diff --git a/src/machines/mill.rs b/src/machines/mill.rs index 6791a8b..17c6513 100644 --- a/src/machines/mill.rs +++ b/src/machines/mill.rs @@ -184,7 +184,7 @@ mod tests { fn setup() -> FSM { let data = Box::new(MillData::default()); - FSM::::new(data) + FSM::new(data) } #[test]