(e: don't miss bluss's answer and their scopedguard crate, below.)
The correct way to achieve this by having code that runs in a destructor, like the defer!
macro you link to does. For anything more than ad-hoc testing I would recommend writing a handle type with a proper destructor, e.g. one interacts with std::sync::Mutex
via its MutexGuard
type (returned by lock
): there's no need to call unlock
on the mutex itself. (The explicit handle-with-destructor approach is more flexible too: it has mutable access to the data, whereas the deferred approach may not be able to, due to Rust's strong aliasing controls.)
In any case, that macro is now (much!) improved due to recent changes, in particular, pnkfelix's sound generic drop work, which removes the necessity for #[unsafe_destructor]
. The direct update would be:
struct ScopeCall<F: FnMut()> {
c: F
}
impl<F: FnMut()> Drop for ScopeCall<F> {
fn drop(&mut self) {
(self.c)();
}
}
macro_rules! defer {
($e:expr) => (
let _scope_call = ScopeCall { c: || -> () { $e; } };
)
}
fn main() {
let x = 42u8;
defer!(println!("defer 1"));
defer!({
println!("defer 2");
println!("inside defer {}", x)
});
println!("normal execution {}", x);
}
Output:
normal execution 42
defer 2
inside defer 42
defer 1
Although, it would be syntactically nicer as:
macro_rules! expr { ($e: expr) => { $e } } // tt hack
macro_rules! defer {
($($data: tt)*) => (
let _scope_call = ScopeCall {
c: || -> () { expr!({ $($data)* }) }
};
)
}
(The tt hack
is necessary due to #5846.)
The use of the generic tt
("token tree") allows one to invoke it without the inner { ... }
when there are multiple statements (i.e. it behaves more like a "normal" control flow structure):
defer! {
println!("defer 2");
println!("inside defer {}", x)
}
Also, for maximum flexibility about what the deferred code can do with captured variables, one could use FnOnce
instead of FnMut
:
struct ScopeCall<F: FnOnce()> {
c: Option<F>
}
impl<F: FnOnce()> Drop for ScopeCall<F> {
fn drop(&mut self) {
self.c.take().unwrap()()
}
}
That will also require constructing the ScopeCall
with a Some
around the value for c
. The Option
dance is required because calling a FnOnce
moves ownership, which isn't possible from behind self: &mut ScopeCall<F>
without it. (Doing this is OK, since the destructor only executes once.)
All in all:
struct ScopeCall<F: FnOnce()> {
c: Option<F>
}
impl<F: FnOnce()> Drop for ScopeCall<F> {
fn drop(&mut self) {
self.c.take().unwrap()()
}
}
macro_rules! expr { ($e: expr) => { $e } } // tt hack
macro_rules! defer {
($($data: tt)*) => (
let _scope_call = ScopeCall {
c: Some(|| -> () { expr!({ $($data)* }) })
};
)
}
fn main() {
let x = 42u8;
defer!(println!("defer 1"));
defer! {
println!("defer 2");
println!("inside defer {}", x)
}
println!("normal execution {}", x);
}
(Same output as the original.)