use std::fmt;
use std::sync::{Arc, Mutex};
pub struct Guard<T, F>
where
F: FnOnce(T),
{
resource: Option<T>,
cleanup: Option<F>,
}
impl<T, F> Guard<T, F>
where
F: FnOnce(T),
{
pub fn new(resource: T, cleanup: F) -> Self {
Self {
resource: Some(resource),
cleanup: Some(cleanup),
}
}
pub fn resource(&self) -> &T {
self.resource
.as_ref()
.expect("Guard resource already consumed")
}
pub fn resource_mut(&mut self) -> &mut T {
self.resource
.as_mut()
.expect("Guard resource already consumed")
}
pub fn into_inner(mut self) -> T {
let resource = self
.resource
.take()
.expect("Guard resource already consumed");
self.cleanup.take(); resource
}
pub fn cleanup_now(mut self) {
if let (Some(resource), Some(cleanup)) = (self.resource.take(), self.cleanup.take()) {
cleanup(resource);
}
}
}
impl<T, F> Drop for Guard<T, F>
where
F: FnOnce(T),
{
fn drop(&mut self) {
if let (Some(resource), Some(cleanup)) = (self.resource.take(), self.cleanup.take()) {
cleanup(resource);
}
}
}
impl<T, F> fmt::Debug for Guard<T, F>
where
T: fmt::Debug,
F: FnOnce(T),
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Guard")
.field("resource", &self.resource)
.field("cleanup", &"<function>")
.finish()
}
}
pub trait ScopedGuard {
type Resource;
type Guard;
fn scoped(resource: Self::Resource) -> Self::Guard;
}
pub struct SharedGuard<T> {
resource: Option<Arc<Mutex<T>>>,
was_locked: bool,
}
impl<T> SharedGuard<T> {
pub fn new(resource: Arc<Mutex<T>>) -> Self {
Self {
resource: Some(resource),
was_locked: false,
}
}
pub fn lock(
&mut self,
) -> Result<std::sync::MutexGuard<'_, T>, std::sync::PoisonError<std::sync::MutexGuard<'_, T>>>
{
if let Some(ref resource) = self.resource {
self.was_locked = true;
resource.lock()
} else {
panic!("SharedGuard resource already consumed")
}
}
pub fn resource(&self) -> &Arc<Mutex<T>> {
self.resource
.as_ref()
.expect("SharedGuard resource already consumed")
}
}
impl<T> Drop for SharedGuard<T> {
fn drop(&mut self) {
self.resource.take();
}
}
pub struct ContextGuard<T, S, C>
where
S: FnOnce(&T),
C: FnOnce(),
{
context: Option<T>,
setter: Option<S>,
clearer: Option<C>,
was_set: bool,
}
impl<T, S, C> ContextGuard<T, S, C>
where
S: FnOnce(&T),
C: FnOnce(),
{
pub fn new(context: T, setter: S, clearer: C) -> Self {
let mut guard = Self {
context: Some(context),
setter: Some(setter),
clearer: Some(clearer),
was_set: false,
};
if let (Some(context), Some(setter)) = (&guard.context, guard.setter.take()) {
setter(context);
guard.was_set = true;
}
guard
}
pub fn context(&self) -> &T {
self.context
.as_ref()
.expect("ContextGuard context already consumed")
}
}
impl<T, S, C> Drop for ContextGuard<T, S, C>
where
S: FnOnce(&T),
C: FnOnce(),
{
fn drop(&mut self) {
if self.was_set {
if let Some(clearer) = self.clearer.take() {
clearer();
}
}
self.context.take();
}
}
pub struct NoOpGuard;
impl NoOpGuard {
pub fn new() -> Self {
Self
}
}
impl Default for NoOpGuard {
fn default() -> Self {
Self::new()
}
}
impl Drop for NoOpGuard {
fn drop(&mut self) {
}
}
#[macro_export]
macro_rules! raii_guard {
($resource:expr_2021, $cleanup:expr_2021) => {
$crate::raii::Guard::new($resource, $cleanup)
};
}
#[macro_export]
macro_rules! context_guard {
($context:expr_2021, $setter:expr_2021, $clearer:expr_2021) => {
$crate::raii::ContextGuard::new($context, $setter, $clearer)
};
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
#[test]
fn test_basic_guard() {
let cleanup_called = Arc::new(AtomicBool::new(false));
let cleanup_called_clone = cleanup_called.clone();
{
let guard = Guard::new(42, move |value| {
assert_eq!(value, 42);
cleanup_called_clone.store(true, Ordering::Relaxed);
});
assert_eq!(*guard.resource(), 42);
assert!(!cleanup_called.load(Ordering::Relaxed));
}
assert!(cleanup_called.load(Ordering::Relaxed));
}
#[test]
fn test_guard_into_inner() {
let cleanup_called = Arc::new(AtomicBool::new(false));
let cleanup_called_clone = cleanup_called.clone();
let guard = Guard::new(42, move |_| {
cleanup_called_clone.store(true, Ordering::Relaxed);
});
let value = guard.into_inner();
assert_eq!(value, 42);
assert!(!cleanup_called.load(Ordering::Relaxed));
}
#[test]
fn test_guard_manual_cleanup() {
let cleanup_called = Arc::new(AtomicBool::new(false));
let cleanup_called_clone = cleanup_called.clone();
let guard = Guard::new(42, move |_| {
cleanup_called_clone.store(true, Ordering::Relaxed);
});
guard.cleanup_now();
assert!(cleanup_called.load(Ordering::Relaxed));
}
#[test]
fn test_shared_guard() {
let data = Arc::new(Mutex::new(42));
let mut guard = SharedGuard::new(data.clone());
{
let locked_data = guard.lock().unwrap();
assert_eq!(*locked_data, 42);
}
let another_lock = guard.lock().unwrap();
assert_eq!(*another_lock, 42);
}
#[test]
fn test_context_guard() {
let context_set = Arc::new(AtomicBool::new(false));
let context_cleared = Arc::new(AtomicBool::new(false));
let context_set_clone = context_set.clone();
let context_cleared_clone = context_cleared.clone();
{
let _guard = ContextGuard::new(
"test_context",
move |_| {
context_set_clone.store(true, Ordering::Relaxed);
},
move || {
context_cleared_clone.store(true, Ordering::Relaxed);
},
);
assert!(context_set.load(Ordering::Relaxed));
assert!(!context_cleared.load(Ordering::Relaxed));
}
assert!(context_cleared.load(Ordering::Relaxed));
}
#[test]
fn test_panic_safety() {
let cleanup_called = Arc::new(AtomicBool::new(false));
let cleanup_called_clone = cleanup_called.clone();
let result = std::panic::catch_unwind(|| {
let _guard = Guard::new(42, move |_| {
cleanup_called_clone.store(true, Ordering::Relaxed);
});
panic!("Test panic");
});
assert!(result.is_err());
assert!(cleanup_called.load(Ordering::Relaxed));
}
#[test]
fn test_no_op_guard() {
let _guard = NoOpGuard::new();
}
#[test]
fn test_macros() {
let cleanup_called = Arc::new(AtomicBool::new(false));
let cleanup_called_clone = cleanup_called.clone();
{
let _guard = raii_guard!(42, move |_| {
cleanup_called_clone.store(true, Ordering::Relaxed);
});
}
assert!(cleanup_called.load(Ordering::Relaxed));
}
}