Greetings friends.
Recently I've had some code like this:
struct ComplexStruct; impl ComplexStruct { fn claim_singleton() -> Result<(), String> { println!("Claiming singleton..."); Ok(()) } fn write_temp() -> Result<(), String> { println!("Writing temporary files..."); Ok(()) } fn check_version() -> Result<(), String> { println!("Checking application version..."); Ok(()) } fn cleanup_temp() -> Result<(), String> { println!("Cleaning up temporary files..."); Ok(()) } fn release_singleton() -> Result<(), String> { println!("Releasing singleton..."); Ok(()) } fn new() -> Result<ComplexStruct, String> { Self::claim_singleton()?; Self::write_temp()?; Self::check_version()?; Ok(ComplexStruct {}) } } impl Drop for ComplexStruct { fn drop(&mut self) { println!("Dropping struct..."); let _ = Self::cleanup_temp(); let _ = Self::release_singleton(); } } fn main() -> Result<(), String> { let _ = ComplexStruct::new()?; Ok(()) } Running this outputs:
Claiming singleton... Writing temporary files... Checking application version... Dropping struct... Cleaning up temporary files... Releasing singleton... However if the version check fails for whatever reason:
fn check_version() -> Result<(), String> { println!("Checking application version..."); Err("Invalid version!".into()) } We now get this:
Claiming singleton... Writing temporary files... Checking application version... Error: "Invalid version!" This isn't great, I have temporary files left over and the singleton still held!
Adding an init_level field like this:
const INIT_LEVEL_NONE: u32 = 0; const INIT_LEVEL_SINGLETON: u32 = 1; const INIT_LEVEL_TEMP: u32 = 2; const INIT_LEVEL_VERSION: u32 = 3; struct ComplexStruct { init_level: u32 } impl ComplexStruct { fn claim_singleton() -> Result<(), String> { println!("Claiming singleton..."); Ok(()) } fn write_temp() -> Result<(), String> { println!("Writing temporary files..."); Ok(()) } fn check_version() -> Result<(), String> { println!("Checking application version..."); Err("Invalid version!".into()) } fn cleanup_temp() -> Result<(), String> { println!("Cleaning up temporary files..."); Ok(()) } fn release_singleton() -> Result<(), String> { println!("Releasing singleton..."); Ok(()) } fn new() -> Result<ComplexStruct, String> { let mut ret = ComplexStruct { init_level: INIT_LEVEL_NONE }; Self::claim_singleton()?; ret.init_level = INIT_LEVEL_SINGLETON; Self::write_temp()?; ret.init_level = INIT_LEVEL_TEMP; Self::check_version()?; ret.init_level = INIT_LEVEL_VERSION; Ok(ret) } } impl Drop for ComplexStruct { fn drop(&mut self) { println!("Dropping struct..."); if self.init_level >= INIT_LEVEL_TEMP { let _ = Self::cleanup_temp(); } if self.init_level >= INIT_LEVEL_SINGLETON { let _ = Self::release_singleton(); } } } fn main() -> Result<(), String> { let _ = ComplexStruct::new()?; Ok(()) } Seems to work, but it's kind of verbose and very C-like. One other attempt I did was to use RAII:
struct Singleton; impl Singleton { fn new() -> Result<Singleton, String> { println!("Claiming singleton..."); Ok(Singleton {}) } } impl Drop for Singleton { fn drop(&mut self) { println!("Releasing singleton..."); } } struct TempFiles; impl TempFiles { fn new() -> Result<TempFiles, String> { println!("Writing temporary files..."); Ok(TempFiles {}) } } impl Drop for TempFiles { fn drop(&mut self) { println!("Cleaning up temporary files..."); } } struct VersionCheck; impl VersionCheck { fn new() -> Result<VersionCheck, String> { println!("Checking application version..."); Err("Invalid version!".into()) } } struct ComplexStruct { _version_check: VersionCheck, _temp_files: TempFiles, _singleton: Singleton, } impl ComplexStruct { fn new() -> Result<ComplexStruct, String> { let singleton = Singleton::new()?; let temp_files = TempFiles::new()?; let version_check = VersionCheck::new()?; Ok(ComplexStruct { _singleton: singleton, _temp_files: temp_files, _version_check: version_check, }) } } impl Drop for ComplexStruct { fn drop(&mut self) { println!("Dropping struct..."); } } fn main() -> Result<(), String> { let _ = ComplexStruct::new()?; Ok(()) } This seems to work but the code is a bit more verbose.
Which is the more Rusty way to do things?
2 posts - 2 participants


