Created
November 15, 2025 15:53
-
-
Save Nekrolm/b840fb23209fe06bd7b392636123016e to your computer and use it in GitHub Desktop.
rust types safe state transitions example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use anyhow; // 1.0.100 | |
| use anyhow::Context; | |
| use anyhow::Result; | |
| use std::fmt::Write; | |
| use std::marker::PhantomData; | |
| use std::mem::ManuallyDrop; | |
| use std::ops::{Deref, DerefMut}; | |
| use std::process::abort; | |
| // struct ScannerOpenParams {}; | |
| /* Correct usage: | |
| * auto scanner = Scanner(...); | |
| * scanner.configure_connection(); | |
| * scanner.connect(); | |
| * scanner.set_scan_parameters(); | |
| * scanner.start() | |
| * for (int i = 0; i < 5; ++i) { | |
| * scanner.scan(); | |
| * } | |
| * scanner.stop(); | |
| */ | |
| // struct Scanner { | |
| // Scanner(ScannerOpenParams) {} | |
| // void configure_connection() {}; | |
| // void connect() {}; | |
| // void set_scan_parameters() {}; | |
| // void start() {}; | |
| // void scan() {}; | |
| // void stop() {}; | |
| // }; | |
| struct ScannerInnerImpl { | |
| connected: bool, | |
| started: bool, | |
| config: String, | |
| connection_config: String, | |
| line: i32, | |
| } | |
| struct ScannerOpenParams { | |
| model_version: i32, | |
| } | |
| #[derive(Debug)] | |
| struct ScanParameters { | |
| exposure: u32, | |
| fov: u32, | |
| } | |
| impl ScannerInnerImpl { | |
| fn open(params: ScannerOpenParams) -> Result<Self> { | |
| anyhow::ensure!(params.model_version <= 42, "unsupported version"); | |
| Ok(Self { | |
| connected: false, | |
| started: false, | |
| config: String::new(), | |
| connection_config: String::new(), | |
| line: 0, | |
| }) | |
| } | |
| fn configure_connection(&mut self, uri: &str) -> Result<()> { | |
| anyhow::ensure!( | |
| !self.connected, | |
| "cannot configure already connected scanner" | |
| ); | |
| anyhow::ensure!(uri.starts_with("scanner://"), "unknown scanner uri"); | |
| self.connection_config.clear(); | |
| self.connection_config.push_str(uri); | |
| Ok(()) | |
| } | |
| fn connect(&mut self) -> Result<()> { | |
| anyhow::ensure!(!self.connected, "already connected"); | |
| anyhow::ensure!( | |
| !self.connection_config.is_empty(), | |
| "connection is not confugured" | |
| ); | |
| println!("connecting scanner {}", self.connection_config); | |
| self.connected = true; | |
| Ok(()) | |
| } | |
| fn disconnect(&mut self) { | |
| if !self.connected { | |
| println!("WARN: scanner is not connected"); | |
| }; | |
| self.connected = false; | |
| } | |
| fn configure_scanner(&mut self, config: ScanParameters) -> Result<()> { | |
| anyhow::ensure!(self.connected, "cannot configure unconnected scanner"); | |
| anyhow::ensure!(!self.started, "cannot configure started scanner"); | |
| anyhow::ensure!( | |
| config.exposure < 600000, | |
| "too long exposure is not supported" | |
| ); | |
| anyhow::ensure!(config.fov < 180, "too wide fov is not supported"); | |
| self.config.clear(); | |
| write!(&mut self.config, "{config:?}")?; | |
| Ok(()) | |
| } | |
| fn start(&mut self) -> Result<()> { | |
| anyhow::ensure!(self.connected, "cannot start unconnected scanner"); | |
| anyhow::ensure!(!self.config.is_empty(), "scanner is not confugured"); | |
| println!("starting scanner with params {}", self.connection_config); | |
| self.started = true; | |
| self.line = 0; | |
| Ok(()) | |
| } | |
| fn scan(&mut self) -> Result<Vec<i32>> { | |
| anyhow::ensure!(self.started, "cannot scan: scanner not started"); | |
| self.line += 1; | |
| Ok((1..=16).map(|x| x * self.line).collect()) | |
| } | |
| fn stop(&mut self) { | |
| if !self.started { | |
| println!("WARN: Scanner is already stopped"); | |
| } | |
| self.started = false; | |
| } | |
| } | |
| impl Drop for ScannerInnerImpl { | |
| fn drop(&mut self) { | |
| if self.started { | |
| println!("WARN: dropping scanner while it is working!!!!!"); | |
| self.stop(); | |
| } | |
| if self.connected { | |
| println!("Disconnecting: {}", self.connection_config); | |
| self.connected = false; | |
| } | |
| } | |
| } | |
| struct Scanner { | |
| inner: OnDropGuard<ScannerInnerImpl, Scanner>, | |
| } | |
| struct ConnectedScanner { | |
| inner: OnDropGuard<ScannerInnerImpl, ConnectedScanner>, | |
| } | |
| struct StartedScanner { | |
| inner: OnDropGuard<ScannerInnerImpl, StartedScanner>, | |
| } | |
| impl DropBehavior<ScannerInnerImpl> for Scanner { | |
| fn on_drop(_: ScannerInnerImpl) {} | |
| } | |
| impl DropBehavior<ScannerInnerImpl> for ConnectedScanner { | |
| fn on_drop(_: ScannerInnerImpl) { | |
| println!("DONT LEAVE SCANNER IN CONNECTED STATE"); | |
| abort() | |
| } | |
| } | |
| impl DropBehavior<ScannerInnerImpl> for StartedScanner { | |
| fn on_drop(_: ScannerInnerImpl) { | |
| println!("DONT LEAVE SCANNER IN STARTED STATE"); | |
| abort() | |
| } | |
| } | |
| impl Scanner { | |
| fn open(params: ScannerOpenParams) -> Result<Self> { | |
| Ok(Self { | |
| inner: ScannerInnerImpl::open(params)?.into(), | |
| }) | |
| } | |
| fn configure_connection(&mut self, uri: &str) -> Result<()> { | |
| self.inner.configure_connection(uri) | |
| } | |
| fn connect(self) -> Result<ConnectedScanner> { | |
| let mut inner = self.inner; | |
| inner.connect()?; | |
| Ok(ConnectedScanner { | |
| inner: inner.rebind(), | |
| }) | |
| } | |
| } | |
| impl ConnectedScanner { | |
| fn configure(&mut self, config: ScanParameters) -> Result<()> { | |
| self.inner.configure_scanner(config) | |
| } | |
| fn start(mut self) -> Result<StartedScanner> { | |
| self.inner.start()?; | |
| Ok(StartedScanner { | |
| inner: self.inner.rebind(), | |
| }) | |
| } | |
| fn disconnect(mut self) -> Scanner { | |
| self.inner.disconnect(); | |
| Scanner { | |
| inner: self.inner.rebind(), | |
| } | |
| } | |
| } | |
| impl StartedScanner { | |
| fn scan(&mut self) -> Result<Vec<i32>> { | |
| self.inner.scan() | |
| } | |
| fn stop(mut self) -> ConnectedScanner { | |
| self.inner.stop(); | |
| ConnectedScanner { | |
| inner: self.inner.rebind(), | |
| } | |
| } | |
| } | |
| trait DropBehavior<T> { | |
| #[inline(always)] | |
| fn on_drop(_: T) {} | |
| } | |
| struct OnDropGuard<T, S: DropBehavior<T>> { | |
| inner: ManuallyDrop<T>, | |
| _behavior: PhantomData<S>, | |
| } | |
| impl<T, S> From<T> for OnDropGuard<T, S> | |
| where | |
| S: DropBehavior<T>, | |
| { | |
| fn from(value: T) -> Self { | |
| Self::new(value) | |
| } | |
| } | |
| impl<T, S> OnDropGuard<T, S> | |
| where | |
| S: DropBehavior<T>, | |
| { | |
| fn new(value: T) -> Self { | |
| Self { | |
| inner: ManuallyDrop::new(value), | |
| _behavior: Default::default(), | |
| } | |
| } | |
| fn into_inner(self) -> T { | |
| let mut guard = ManuallyDrop::new(self); | |
| unsafe { ManuallyDrop::take(&mut guard.inner) } | |
| } | |
| fn rebind<U: DropBehavior<T>>(self) -> OnDropGuard<T, U> { | |
| OnDropGuard::new(self.into_inner()) | |
| } | |
| } | |
| impl<T, S> Deref for OnDropGuard<T, S> | |
| where | |
| S: DropBehavior<T>, | |
| { | |
| type Target = T; | |
| fn deref(&self) -> &Self::Target { | |
| &self.inner | |
| } | |
| } | |
| impl<T, S> DerefMut for OnDropGuard<T, S> | |
| where | |
| S: DropBehavior<T>, | |
| { | |
| fn deref_mut(&mut self) -> &mut Self::Target { | |
| &mut self.inner | |
| } | |
| } | |
| impl<T, S> Drop for OnDropGuard<T, S> | |
| where | |
| S: DropBehavior<T>, | |
| { | |
| fn drop(&mut self) { | |
| S::on_drop(unsafe { ManuallyDrop::take(&mut self.inner) }) | |
| } | |
| } | |
| enum MyScannerImpl { | |
| Base(Scanner), | |
| Connected(ConnectedScanner), | |
| Started(StartedScanner), | |
| } | |
| struct MyScanner(Option<MyScannerImpl>); | |
| trait DynScanner { | |
| fn configure_connection(&mut self, uri: &str) -> Result<()>; | |
| fn connect(&mut self) -> Result<()>; | |
| fn disconnect(&mut self) -> Result<()>; | |
| fn configure_scanner(&mut self, config: ScanParameters) -> Result<()>; | |
| fn start(&mut self) -> Result<()>; | |
| fn scan(&mut self) -> Result<Vec<i32>>; | |
| fn stop(&mut self) -> Result<()>; | |
| } | |
| // fn mutate_with<T>(val: &mut T, mutator: impl FnOnce(T) -> T) { | |
| // let new_val = match std::panic::catch_unwind(AssertUnwindSafe(|| { | |
| // mutator(unsafe { std::ptr::read(val) }) | |
| // })) { | |
| // Ok(val) => val, | |
| // Err(_) => abort(), | |
| // }; | |
| // unsafe { std::ptr::write(val, new_val) }; | |
| // } | |
| impl MyScanner { | |
| fn open(params: ScannerOpenParams) -> Result<Self> { | |
| Scanner::open(params) | |
| .map(MyScannerImpl::Base) | |
| .map(Some) | |
| .map(Self) | |
| } | |
| } | |
| impl DynScanner for MyScanner { | |
| fn configure_connection(&mut self, uri: &str) -> Result<()> { | |
| let state = self.0.as_mut().context("Broken state")?; | |
| let MyScannerImpl::Base(scanner) = state else { | |
| anyhow::bail!("Cannot configure connection not in base state"); | |
| }; | |
| scanner.configure_connection(uri)?; | |
| Ok(()) | |
| } | |
| fn connect(&mut self) -> Result<()> { | |
| let state = self.0.take().context("Broken state")?; | |
| let MyScannerImpl::Base(scanner) = state else { | |
| anyhow::bail!("Cannot connect not in base state"); | |
| }; | |
| let connected = scanner.connect()?; | |
| let _ = self.0.insert(MyScannerImpl::Connected(connected)); | |
| Ok(()) | |
| } | |
| fn disconnect(&mut self) -> Result<()> { | |
| let state = self.0.take().context("Broken state")?; | |
| let MyScannerImpl::Connected(scanner) = state else { | |
| anyhow::bail!("Cannot disconnect not in connected state"); | |
| }; | |
| let connected = scanner.disconnect(); | |
| let _ = self.0.insert(MyScannerImpl::Base(connected)); | |
| Ok(()) | |
| } | |
| fn configure_scanner(&mut self, config: ScanParameters) -> Result<()> { | |
| let state = self.0.as_mut().context("Broken state")?; | |
| let MyScannerImpl::Connected(scanner) = state else { | |
| anyhow::bail!("Cannot configure scanner not in connected state"); | |
| }; | |
| scanner.configure(config) | |
| } | |
| fn start(&mut self) -> Result<()> { | |
| let state = self.0.take().context("Broken state")?; | |
| let MyScannerImpl::Connected(scanner) = state else { | |
| anyhow::bail!("Cannot start not in connected state"); | |
| }; | |
| let connected = scanner.start()?; | |
| let _ = self.0.insert(MyScannerImpl::Started(connected)); | |
| Ok(()) | |
| } | |
| fn scan(&mut self) -> Result<Vec<i32>> { | |
| let state = self.0.as_mut().context("Broken state")?; | |
| let MyScannerImpl::Started(scanner) = state else { | |
| anyhow::bail!("Cannot scan not in started state"); | |
| }; | |
| scanner.scan() | |
| } | |
| fn stop(&mut self) -> Result<()> { | |
| let state = self.0.take().context("Broken state")?; | |
| let MyScannerImpl::Started(scanner) = state else { | |
| anyhow::bail!("Cannot stop not in started state"); | |
| }; | |
| let connected = scanner.stop(); | |
| let _ = self.0.insert(MyScannerImpl::Connected(connected)); | |
| Ok(()) | |
| } | |
| } | |
| fn open_scanner() -> Result<Box<dyn DynScanner>> { | |
| let scanner = MyScanner::open(ScannerOpenParams { model_version: 33 })?; | |
| Ok(Box::new(scanner)) | |
| } | |
| fn main() -> Result<()> { | |
| let mut scanners = Vec::<Box<dyn DynScanner>>::new(); | |
| for _ in 0..5 { | |
| scanners.push( | |
| open_scanner()? | |
| ); | |
| } | |
| for scanner in &mut scanners { | |
| scanner.configure_connection("scanner://ololo")?; | |
| scanner.connect()?; | |
| scanner.configure_scanner(ScanParameters { exposure: 55, fov: 33 })?; | |
| scanner.start()?; | |
| } | |
| for _ in 0..5 { | |
| for scanner in &mut scanners { | |
| println!("{:?}", scanner.scan()); | |
| } | |
| } | |
| for mut scanner in scanners { | |
| scanner.stop()?; | |
| scanner.disconnect()?; | |
| } | |
| Ok(()) | |
| // let mut scanner = Scanner::open(ScannerOpenParams { model_version: 33 })?; | |
| // scanner.configure_connection("scanner://best-scanner")?; | |
| // let mut connected_scanner = scanner.connect()?; | |
| // connected_scanner.configure(ScanParameters { | |
| // exposure: 10, | |
| // fov: 90, | |
| // })?; | |
| // let mut ready_scanner = connected_scanner.start()?; | |
| // for _ in 0..5 { | |
| // let data = ready_scanner.scan()?; | |
| // println!("{data:?}"); | |
| // } | |
| // ready_scanner.stop().disconnect(); | |
| // Ok(()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment