rustrails_record/
no_touching.rs1use std::cell::Cell;
2
3thread_local! {
4 static NO_TOUCHING_DEPTH: Cell<usize> = const { Cell::new(0) };
5}
6
7pub struct NoTouching;
9
10impl NoTouching {
11 pub fn apply<F, R>(f: F) -> R
13 where
14 F: FnOnce() -> R,
15 {
16 struct Guard;
17
18 impl Drop for Guard {
19 fn drop(&mut self) {
20 NO_TOUCHING_DEPTH.with(|depth| depth.set(depth.get().saturating_sub(1)));
21 }
22 }
23
24 NO_TOUCHING_DEPTH.with(|depth| depth.set(depth.get() + 1));
25 let _guard = Guard;
26 f()
27 }
28
29 #[must_use]
31 pub fn is_active() -> bool {
32 NO_TOUCHING_DEPTH.with(|depth| depth.get() > 0)
33 }
34}
35
36pub(crate) fn is_disabled() -> bool {
37 NoTouching::is_active()
38}
39
40#[cfg(test)]
41mod tests {
42 use std::thread;
43
44 use super::{NoTouching, is_disabled};
45
46 #[test]
47 fn apply_enables_no_touching_for_block() {
48 let active = NoTouching::apply(NoTouching::is_active);
49 assert!(active);
50 assert!(!NoTouching::is_active());
51 }
52
53 #[test]
54 fn apply_restores_previous_state_after_return() {
55 NoTouching::apply(|| {
56 assert!(NoTouching::is_active());
57 });
58
59 assert!(!NoTouching::is_active());
60 }
61
62 #[test]
63 fn apply_restores_state_after_panic() {
64 let result = std::panic::catch_unwind(|| {
65 NoTouching::apply(|| panic!("boom"));
66 });
67
68 assert!(result.is_err());
69 assert!(!NoTouching::is_active());
70 }
71
72 #[test]
73 fn nested_apply_keeps_no_touching_active_until_outer_scope_exits() {
74 NoTouching::apply(|| {
75 assert!(NoTouching::is_active());
76 NoTouching::apply(|| assert!(NoTouching::is_active()));
77 assert!(NoTouching::is_active());
78 });
79
80 assert!(!NoTouching::is_active());
81 }
82
83 #[test]
84 fn state_is_thread_local() {
85 let handle = thread::spawn(NoTouching::is_active);
86 NoTouching::apply(|| {
87 assert!(NoTouching::is_active());
88 assert!(!handle.join().expect("thread should complete"));
89 });
90 }
91
92 #[test]
93 fn crate_helper_tracks_public_state() {
94 assert_eq!(is_disabled(), NoTouching::is_active());
95 }
96}