While experimenting with scope rules in Rust, I ended up implementing a recurrent idea I had – a RAII guard that runs a function when the object goes out of scope. I’m well aware of scopeguard crate, but it has some stinky unsafe bits in its implementation.
I was never able to properly implement it, probably because my Rust skills weren’t good enough, but now I did. And I’m rather satisfied with it:
use std::ops::Deref; /// Returns the object wrapped in a way that the associated closure will run /// when it goes out of scope. pub struct Guard<T, D: FnOnce(&mut T)> { asset: T, dropper: Option<D>, } impl<T, D: FnOnce(&mut T)> Drop for Guard<T, D> { fn drop(&mut self) { self.dropper.take() .map(|d| d(&mut self.asset)); } } impl<T, D: FnOnce(&mut T)> Deref for Guard<T, D> { type Target = T; fn deref(&self) -> &Self::Target { &self.asset } } impl<T, D: FnOnce(&mut T)> Guard<T, D> { /// Creates a new `Guard` object. pub fn new(asset: T, dropper: D) -> Guard<T, D> { Self { asset, dropper: Some(dropper) } } }
I though about writing stuff like this in WinSafe:
impl HWND { pub fn GetDC(self) -> WinResult<Guard<HDC, impl FnOnce(&mut HDC)>> { Ok(Guard::new( self.GetDC()?, move |hdc| { self.ReleaseDC(*hdc).expect("Guard crash."); }, )) } }
But then there are big implications like CreateMenu
, which may or may not require a DestroyMenu
call. These dubious behaviors are very unsettling, and many questions arise:
- Should I write another method, with a
_guarded
suffix? - Mark the unguarded original as
unsafe
? - In the example above,
self
is copied into the closure – what if it’s still zero?
So by now I believe I’ll leave everything as it is, and let the defer-lite crate at hand when needed.
Still, I greatly miss the idiomatic defer
in Go.