#![cfg_attr(not(any(test, feature = "use_std")), no_std)]
#![doc(html_root_url = "https://docs.rs/scopeguard/1/")]
#[cfg(not(any(test, feature = "use_std")))]
extern crate core as std;
use std::fmt;
use std::marker::PhantomData;
use std::mem::{self, ManuallyDrop};
use std::ops::{Deref, DerefMut};
use std::ptr;
pub trait Strategy {
fn should_run() -> bool;
}
#[derive(Debug)]
pub enum Always {}
#[cfg(feature = "use_std")]
#[derive(Debug)]
pub enum OnUnwind {}
#[cfg(feature = "use_std")]
#[derive(Debug)]
pub enum OnSuccess {}
impl Strategy for Always {
#[inline(always)]
fn should_run() -> bool { true }
}
#[cfg(feature = "use_std")]
impl Strategy for OnUnwind {
#[inline]
fn should_run() -> bool { std::thread::panicking() }
}
#[cfg(feature = "use_std")]
impl Strategy for OnSuccess {
#[inline]
fn should_run() -> bool { !std::thread::panicking() }
}
#[macro_export]
macro_rules! defer {
($($t:tt)*) => {
let _guard = $crate::guard((), |()| { $($t)* });
};
}
#[cfg(feature = "use_std")]
#[macro_export]
macro_rules! defer_on_success {
($($t:tt)*) => {
let _guard = $crate::guard_on_success((), |()| { $($t)* });
};
}
#[cfg(feature = "use_std")]
#[macro_export]
macro_rules! defer_on_unwind {
($($t:tt)*) => {
let _guard = $crate::guard_on_unwind((), |()| { $($t)* });
};
}
pub struct ScopeGuard<T, F, S = Always>
where F: FnOnce(T),
S: Strategy,
{
value: ManuallyDrop<T>,
dropfn: ManuallyDrop<F>,
strategy: PhantomData<fn(S) -> S>,
}
impl<T, F, S> ScopeGuard<T, F, S>
where F: FnOnce(T),
S: Strategy,
{
#[inline]
pub fn with_strategy(v: T, dropfn: F) -> ScopeGuard<T, F, S> {
ScopeGuard {
value: ManuallyDrop::new(v),
dropfn: ManuallyDrop::new(dropfn),
strategy: PhantomData,
}
}
#[inline]
pub fn into_inner(guard: Self) -> T {
unsafe {
let value = ptr::read(&*guard.value);
let _dropfn = ptr::read(&*guard.dropfn);
mem::forget(guard);
value
}
}
}
#[inline]
pub fn guard<T, F>(v: T, dropfn: F) -> ScopeGuard<T, F, Always>
where F: FnOnce(T)
{
ScopeGuard::with_strategy(v, dropfn)
}
#[cfg(feature = "use_std")]
#[inline]
pub fn guard_on_success<T, F>(v: T, dropfn: F) -> ScopeGuard<T, F, OnSuccess>
where F: FnOnce(T)
{
ScopeGuard::with_strategy(v, dropfn)
}
#[cfg(feature = "use_std")]
#[inline]
pub fn guard_on_unwind<T, F>(v: T, dropfn: F) -> ScopeGuard<T, F, OnUnwind>
where F: FnOnce(T)
{
ScopeGuard::with_strategy(v, dropfn)
}
unsafe impl<T, F, S> Sync for ScopeGuard<T, F, S>
where T: Sync,
F: FnOnce(T),
S: Strategy
{}
impl<T, F, S> Deref for ScopeGuard<T, F, S>
where F: FnOnce(T),
S: Strategy
{
type Target = T;
fn deref(&self) -> &T {
&*self.value
}
}
impl<T, F, S> DerefMut for ScopeGuard<T, F, S>
where F: FnOnce(T),
S: Strategy
{
fn deref_mut(&mut self) -> &mut T {
&mut *self.value
}
}
impl<T, F, S> Drop for ScopeGuard<T, F, S>
where F: FnOnce(T),
S: Strategy
{
fn drop(&mut self) {
let (value, dropfn) = unsafe {
(ptr::read(&*self.value), ptr::read(&*self.dropfn))
};
if S::should_run() {
dropfn(value);
}
}
}
impl<T, F, S> fmt::Debug for ScopeGuard<T, F, S>
where T: fmt::Debug,
F: FnOnce(T),
S: Strategy
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct(stringify!(ScopeGuard))
.field("value", &*self.value)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
use std::panic::catch_unwind;
use std::panic::AssertUnwindSafe;
#[test]
fn test_defer() {
let drops = Cell::new(0);
defer!(drops.set(1000));
assert_eq!(drops.get(), 0);
}
#[cfg(feature = "use_std")]
#[test]
fn test_defer_success_1() {
let drops = Cell::new(0);
{
defer_on_success!(drops.set(1));
assert_eq!(drops.get(), 0);
}
assert_eq!(drops.get(), 1);
}
#[cfg(feature = "use_std")]
#[test]
fn test_defer_success_2() {
let drops = Cell::new(0);
let _ = catch_unwind(AssertUnwindSafe(|| {
defer_on_success!(drops.set(1));
panic!("failure")
}));
assert_eq!(drops.get(), 0);
}
#[cfg(feature = "use_std")]
#[test]
fn test_defer_unwind_1() {
let drops = Cell::new(0);
let _ = catch_unwind(AssertUnwindSafe(|| {
defer_on_unwind!(drops.set(1));
assert_eq!(drops.get(), 0);
panic!("failure")
}));
assert_eq!(drops.get(), 1);
}
#[cfg(feature = "use_std")]
#[test]
fn test_defer_unwind_2() {
let drops = Cell::new(0);
{
defer_on_unwind!(drops.set(1));
}
assert_eq!(drops.get(), 0);
}
#[test]
fn test_only_dropped_by_closure_when_run() {
let value_drops = Cell::new(0);
let value = guard((), |()| value_drops.set(1 + value_drops.get()));
let closure_drops = Cell::new(0);
let guard = guard(value, |_| closure_drops.set(1 + closure_drops.get()));
assert_eq!(value_drops.get(), 0);
assert_eq!(closure_drops.get(), 0);
drop(guard);
assert_eq!(value_drops.get(), 1);
assert_eq!(closure_drops.get(), 1);
}
#[cfg(feature = "use_std")]
#[test]
fn test_dropped_once_when_not_run() {
let value_drops = Cell::new(0);
let value = guard((), |()| value_drops.set(1 + value_drops.get()));
let captured_drops = Cell::new(0);
let captured = guard((), |()| captured_drops.set(1 + captured_drops.get()));
let closure_drops = Cell::new(0);
let guard = guard_on_unwind(value, |value| {
drop(value);
drop(captured);
closure_drops.set(1 + closure_drops.get())
});
assert_eq!(value_drops.get(), 0);
assert_eq!(captured_drops.get(), 0);
assert_eq!(closure_drops.get(), 0);
drop(guard);
assert_eq!(value_drops.get(), 1);
assert_eq!(captured_drops.get(), 1);
assert_eq!(closure_drops.get(), 0);
}
#[test]
fn test_into_inner() {
let dropped = Cell::new(false);
let value = guard(42, |_| dropped.set(true));
let guard = guard(value, |_| dropped.set(true));
let inner = ScopeGuard::into_inner(guard);
assert_eq!(dropped.get(), false);
assert_eq!(*inner, 42);
}
}