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.