ferroui_base/utilities/
thread_bound.rs1use std::mem::ManuallyDrop;
2use std::thread::{self, ThreadId};
3
4pub struct ThreadBound<T> {
17 value: ManuallyDrop<T>,
18 thread: ThreadId,
19}
20
21unsafe impl<T> Send for ThreadBound<T> {}
24unsafe impl<T> Sync for ThreadBound<T> {}
25
26impl<T> ThreadBound<T> {
27 pub fn new(value: T) -> Self {
29 Self { value: ManuallyDrop::new(value), thread: thread::current().id() }
30 }
31
32 pub fn is_on_thread(&self) -> bool {
34 thread::current().id() == self.thread
35 }
36
37 pub fn get(&self) -> &T {
43 assert!(self.is_on_thread(), "the value is bound to the thread that created it");
44 &self.value
45 }
46
47 pub fn into_inner(self) -> T {
53 assert!(self.is_on_thread(), "the value is bound to the thread that created it");
54 let mut this = ManuallyDrop::new(self);
55 unsafe { ManuallyDrop::take(&mut this.value) }
57 }
58}
59
60impl<T> Drop for ThreadBound<T> {
61 fn drop(&mut self) {
62 if self.is_on_thread() {
63 unsafe { ManuallyDrop::drop(&mut self.value) };
65 }
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
73 use std::rc::Rc;
74 use std::sync::Arc;
75
76 #[test]
77 fn the_value_is_reached_and_dropped_on_its_thread() {
78 let value = Rc::new(5);
79 let bound = ThreadBound::new(value.clone());
80
81 assert_eq!(5, **bound.get());
82 drop(bound);
83 assert_eq!(1, Rc::strong_count(&value));
84 }
85
86 #[test]
87 fn another_thread_cannot_reach_the_value_and_leaks_it() {
88 let value = Rc::new(5);
89 let bound = Arc::new(ThreadBound::new(value.clone()));
90
91 let moved = bound.clone();
92 let reached = thread::spawn(move || std::panic::catch_unwind(|| moved.is_on_thread()).unwrap_or(true))
93 .join()
94 .unwrap();
95 assert!(!reached);
96
97 let moved = bound;
98 thread::spawn(move || drop(moved)).join().unwrap();
99 assert_eq!(2, Rc::strong_count(&value));
101 }
102}