use crate::raii::Guard;
pub fn with_context<T, F, R>(context: T, f: F) -> R
where
F: FnOnce(&T) -> R,
{
f(&context)
}
pub fn with_setup_cleanup<S, W, C, T, R>(setup: S, work: W, cleanup: C) -> R
where
S: FnOnce() -> T,
W: FnOnce(&T) -> R,
C: FnOnce(T),
{
let resource = setup();
let _guard = Guard::new(resource, cleanup);
work(_guard.resource())
}
pub fn with_optional_scope<S, W, C, T, R>(condition: bool, setup: S, work: W, cleanup: C) -> R
where
S: FnOnce() -> T,
W: FnOnce(Option<&T>) -> R,
C: FnOnce(T),
{
if condition {
let resource = setup();
let _guard = Guard::new(resource, cleanup);
work(Some(_guard.resource()))
} else {
work(None)
}
}
pub trait ScopedCallback<T> {
fn with_scope<F, R>(self, f: F) -> R
where
F: FnOnce(&T) -> R;
}
pub struct ScopedBuilder<T> {
resources: Vec<T>,
cleanups: Vec<Box<dyn FnOnce(T)>>,
}
impl<T> ScopedBuilder<T> {
pub fn new() -> Self {
Self {
resources: Vec::new(),
cleanups: Vec::new(),
}
}
pub fn with_resource<F>(mut self, resource: T, cleanup: F) -> Self
where
F: FnOnce(T) + 'static,
{
self.resources.push(resource);
self.cleanups.push(Box::new(cleanup));
self
}
pub fn execute<F, R>(mut self, work: F) -> R
where
F: FnOnce(&[T]) -> R,
{
struct CleanupGuard<T> {
resources: Vec<T>,
cleanups: Vec<Box<dyn FnOnce(T)>>,
}
impl<T> Drop for CleanupGuard<T> {
fn drop(&mut self) {
while let (Some(resource), Some(cleanup)) =
(self.resources.pop(), self.cleanups.pop())
{
cleanup(resource);
}
}
}
let guard = CleanupGuard {
resources: std::mem::take(&mut self.resources),
cleanups: std::mem::take(&mut self.cleanups),
};
work(&guard.resources)
}
}
impl<T> Default for ScopedBuilder<T> {
fn default() -> Self {
Self::new()
}
}
pub fn with_multiple_contexts<T, S, W, C, R>(contexts: Vec<T>, setup: S, work: W, cleanup: C) -> R
where
T: Clone,
S: Fn(&T),
W: FnOnce(&[T]) -> R,
C: Fn(&T),
{
for context in &contexts {
setup(context);
}
let contexts_for_cleanup = contexts.clone();
let _guard = Guard::new(contexts_for_cleanup, move |contexts| {
for context in contexts.iter().rev() {
cleanup(context);
}
});
work(&contexts)
}
#[macro_export]
macro_rules! scoped_operation {
(
setup => $setup:expr_2021,
work => $work:expr_2021,
cleanup => $cleanup:expr_2021
) => {
$crate::scoped::with_setup_cleanup($setup, $work, $cleanup)
};
}
#[macro_export]
macro_rules! with_scoped_context {
($context:expr_2021, $work:expr_2021) => {
$crate::scoped::with_context($context, $work)
};
}
pub trait IntoScoped {
type Resource;
fn into_scoped(self) -> Self::Resource;
fn scoped<F, R>(self, f: F) -> R
where
Self: Sized,
F: FnOnce(&Self::Resource) -> R,
{
let resource = self.into_scoped();
f(&resource)
}
}
impl<T> IntoScoped for T {
type Resource = T;
fn into_scoped(self) -> Self::Resource {
self
}
}
pub fn with_error_scope<S, W, C, T, R, E>(setup: S, work: W, cleanup: C) -> Result<R, E>
where
S: FnOnce() -> Result<T, E>,
W: FnOnce(&T) -> Result<R, E>,
C: FnOnce(T),
{
let resource = setup()?;
let _guard = Guard::new(resource, cleanup);
work(_guard.resource())
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
#[test]
fn test_with_context() {
let result = with_context(42, |ctx| *ctx + 10);
assert_eq!(result, 52);
}
#[test]
fn test_with_setup_cleanup() {
let cleanup_called = Arc::new(AtomicUsize::new(0));
let cleanup_called_clone = cleanup_called.clone();
let result = with_setup_cleanup(
|| 42,
|resource| *resource + 10,
move |resource| {
cleanup_called_clone.store(resource, Ordering::Relaxed);
},
);
assert_eq!(result, 52);
assert_eq!(cleanup_called.load(Ordering::Relaxed), 42);
}
#[test]
fn test_with_optional_scope() {
let result = with_optional_scope(
true,
|| 42,
|resource| resource.map(|r| *r + 10).unwrap_or(0),
|_| {}, );
assert_eq!(result, 52);
let result = with_optional_scope(
false,
|| 42,
|resource| resource.map(|r| *r + 10).unwrap_or(0),
|_| {}, );
assert_eq!(result, 0);
}
#[test]
fn test_scoped_builder() {
let result = ScopedBuilder::new()
.with_resource("test1", |_| {})
.with_resource("test2", |_| {})
.execute(|resources| resources.len());
assert_eq!(result, 2);
}
#[test]
fn test_with_multiple_contexts() {
let setup_count = Arc::new(AtomicUsize::new(0));
let cleanup_count = Arc::new(AtomicUsize::new(0));
let setup_count_clone = setup_count.clone();
let cleanup_count_clone = cleanup_count.clone();
let contexts = vec![1, 2, 3];
let result = with_multiple_contexts(
contexts,
move |_| {
setup_count_clone.fetch_add(1, Ordering::Relaxed);
},
|contexts| contexts.len(),
move |_| {
cleanup_count_clone.fetch_add(1, Ordering::Relaxed);
},
);
assert_eq!(result, 3);
assert_eq!(setup_count.load(Ordering::Relaxed), 3);
assert_eq!(cleanup_count.load(Ordering::Relaxed), 3);
}
#[test]
fn test_with_error_scope() {
let result: Result<i32, &str> = with_error_scope(
|| Ok(42),
|resource| Ok(*resource + 10),
|_| {}, );
assert_eq!(result, Ok(52));
let result: Result<i32, &str> = with_error_scope(
|| Err("setup failed"),
|resource| Ok(*resource + 10),
|_: i32| {}, );
assert_eq!(result, Err("setup failed"));
let result: Result<i32, &str> = with_error_scope(
|| Ok(42),
|_| Err("work failed"),
|_| {}, );
assert_eq!(result, Err("work failed"));
}
#[test]
fn test_into_scoped() {
let result = 42.scoped(|resource| *resource + 10);
assert_eq!(result, 52);
}
#[test]
fn test_panic_safety_in_scoped_operations() {
let cleanup_called = Arc::new(AtomicUsize::new(0));
let cleanup_called_clone = cleanup_called.clone();
let result = std::panic::catch_unwind(|| {
with_setup_cleanup(
|| 42,
|_| panic!("test panic"),
move |resource| {
cleanup_called_clone.store(resource, Ordering::Relaxed);
},
)
});
assert!(result.is_err());
assert_eq!(cleanup_called.load(Ordering::Relaxed), 42);
}
#[test]
fn test_scoped_builder_cleanups_called() {
let counter = Arc::new(AtomicUsize::new(0));
let c1 = counter.clone();
let c2 = counter.clone();
let c3 = counter.clone();
let result = ScopedBuilder::new()
.with_resource(10, move |_| {
c1.fetch_add(1, Ordering::SeqCst);
})
.with_resource(20, move |_| {
c2.fetch_add(1, Ordering::SeqCst);
})
.with_resource(30, move |_| {
c3.fetch_add(1, Ordering::SeqCst);
})
.execute(|resources| {
assert_eq!(resources, &[10, 20, 30]);
resources.iter().sum::<i32>()
});
assert_eq!(result, 60);
assert_eq!(counter.load(Ordering::SeqCst), 3);
}
#[test]
fn test_scoped_builder_lifo_cleanup_order() {
let order = Arc::new(Mutex::new(Vec::new()));
let o1 = order.clone();
let o2 = order.clone();
let o3 = order.clone();
ScopedBuilder::new()
.with_resource("first", move |r| {
o1.lock().unwrap().push(r);
})
.with_resource("second", move |r| {
o2.lock().unwrap().push(r);
})
.with_resource("third", move |r| {
o3.lock().unwrap().push(r);
})
.execute(|_| {});
let cleaned = order.lock().unwrap();
assert_eq!(&*cleaned, &["third", "second", "first"]);
}
#[test]
fn test_scoped_builder_panic_safety() {
let counter = Arc::new(AtomicUsize::new(0));
let c1 = counter.clone();
let c2 = counter.clone();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
ScopedBuilder::new()
.with_resource(1, move |_| {
c1.fetch_add(1, Ordering::SeqCst);
})
.with_resource(2, move |_| {
c2.fetch_add(1, Ordering::SeqCst);
})
.execute(|_| panic!("work panicked"))
}));
assert!(result.is_err());
assert_eq!(counter.load(Ordering::SeqCst), 2);
}
}