use std::cell::Cell;
thread_local! {
static NO_TOUCHING_DEPTH: Cell<usize> = const { Cell::new(0) };
}
pub struct NoTouching;
impl NoTouching {
pub fn apply<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
struct Guard;
impl Drop for Guard {
fn drop(&mut self) {
NO_TOUCHING_DEPTH.with(|depth| depth.set(depth.get().saturating_sub(1)));
}
}
NO_TOUCHING_DEPTH.with(|depth| depth.set(depth.get() + 1));
let _guard = Guard;
f()
}
#[must_use]
pub fn is_active() -> bool {
NO_TOUCHING_DEPTH.with(|depth| depth.get() > 0)
}
}
pub(crate) fn is_disabled() -> bool {
NoTouching::is_active()
}
#[cfg(test)]
mod tests {
use std::thread;
use super::{NoTouching, is_disabled};
#[test]
fn apply_enables_no_touching_for_block() {
let active = NoTouching::apply(NoTouching::is_active);
assert!(active);
assert!(!NoTouching::is_active());
}
#[test]
fn apply_restores_previous_state_after_return() {
NoTouching::apply(|| {
assert!(NoTouching::is_active());
});
assert!(!NoTouching::is_active());
}
#[test]
fn apply_restores_state_after_panic() {
let result = std::panic::catch_unwind(|| {
NoTouching::apply(|| panic!("boom"));
});
assert!(result.is_err());
assert!(!NoTouching::is_active());
}
#[test]
fn nested_apply_keeps_no_touching_active_until_outer_scope_exits() {
NoTouching::apply(|| {
assert!(NoTouching::is_active());
NoTouching::apply(|| assert!(NoTouching::is_active()));
assert!(NoTouching::is_active());
});
assert!(!NoTouching::is_active());
}
#[test]
fn state_is_thread_local() {
let handle = thread::spawn(NoTouching::is_active);
NoTouching::apply(|| {
assert!(NoTouching::is_active());
assert!(!handle.join().expect("thread should complete"));
});
}
#[test]
fn crate_helper_tracks_public_state() {
assert_eq!(is_disabled(), NoTouching::is_active());
}
}