scoped_tls/
lib.rs

1// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution and at
3// http://rust-lang.org/COPYRIGHT.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11//! Scoped thread-local storage
12//!
13//! This module provides the ability to generate *scoped* thread-local
14//! variables. In this sense, scoped indicates that thread local storage
15//! actually stores a reference to a value, and this reference is only placed
16//! in storage for a scoped amount of time.
17//!
18//! There are no restrictions on what types can be placed into a scoped
19//! variable, but all scoped variables are initialized to the equivalent of
20//! null. Scoped thread local storage is useful when a value is present for a known
21//! period of time and it is not required to relinquish ownership of the
22//! contents.
23//!
24//! # Examples
25//!
26//! ```
27//! #[macro_use]
28//! extern crate scoped_tls;
29//!
30//! scoped_thread_local!(static FOO: u32);
31//!
32//! # fn main() {
33//! // Initially each scoped slot is empty.
34//! assert!(!FOO.is_set());
35//!
36//! // When inserting a value, the value is only in place for the duration
37//! // of the closure specified.
38//! FOO.set(&1, || {
39//!     FOO.with(|slot| {
40//!         assert_eq!(*slot, 1);
41//!     });
42//! });
43//! # }
44//! ```
45
46#![deny(missing_docs, warnings)]
47
48use std::cell::Cell;
49use std::marker;
50use std::thread::LocalKey;
51
52/// The macro. See the module level documentation for the description and examples.
53#[macro_export]
54macro_rules! scoped_thread_local {
55    ($(#[$attrs:meta])* $vis:vis static $name:ident: $ty:ty) => (
56        $(#[$attrs])*
57        $vis static $name: $crate::ScopedKey<$ty> = $crate::ScopedKey {
58            inner: {
59                ::std::thread_local!(static FOO: ::std::cell::Cell<*const ()> = const {
60                    ::std::cell::Cell::new(::std::ptr::null())
61                });
62                &FOO
63            },
64            _marker: ::std::marker::PhantomData,
65        };
66    )
67}
68
69/// Type representing a thread local storage key corresponding to a reference
70/// to the type parameter `T`.
71///
72/// Keys are statically allocated and can contain a reference to an instance of
73/// type `T` scoped to a particular lifetime. Keys provides two methods, `set`
74/// and `with`, both of which currently use closures to control the scope of
75/// their contents.
76pub struct ScopedKey<T> {
77    #[doc(hidden)]
78    pub inner: &'static LocalKey<Cell<*const ()>>,
79    #[doc(hidden)]
80    pub _marker: marker::PhantomData<T>,
81}
82
83unsafe impl<T> Sync for ScopedKey<T> {}
84
85impl<T> ScopedKey<T> {
86    /// Inserts a value into this scoped thread local storage slot for a
87    /// duration of a closure.
88    ///
89    /// While `f` is running, the value `t` will be returned by `get` unless
90    /// this function is called recursively inside of `f`.
91    ///
92    /// Upon return, this function will restore the previous value, if any
93    /// was available.
94    ///
95    /// # Examples
96    ///
97    /// ```
98    /// #[macro_use]
99    /// extern crate scoped_tls;
100    ///
101    /// scoped_thread_local!(static FOO: u32);
102    ///
103    /// # fn main() {
104    /// FOO.set(&100, || {
105    ///     let val = FOO.with(|v| *v);
106    ///     assert_eq!(val, 100);
107    ///
108    ///     // set can be called recursively
109    ///     FOO.set(&101, || {
110    ///         // ...
111    ///     });
112    ///
113    ///     // Recursive calls restore the previous value.
114    ///     let val = FOO.with(|v| *v);
115    ///     assert_eq!(val, 100);
116    /// });
117    /// # }
118    /// ```
119    pub fn set<F, R>(&'static self, t: &T, f: F) -> R
120        where F: FnOnce() -> R
121    {
122        struct Reset {
123            key: &'static LocalKey<Cell<*const ()>>,
124            val: *const (),
125        }
126        impl Drop for Reset {
127            fn drop(&mut self) {
128                self.key.with(|c| c.set(self.val));
129            }
130        }
131        let prev = self.inner.with(|c| {
132            let prev = c.get();
133            c.set(t as *const T as *const ());
134            prev
135        });
136        let _reset = Reset { key: self.inner, val: prev };
137        f()
138    }
139
140    /// Gets a value out of this scoped variable.
141    ///
142    /// This function takes a closure which receives the value of this
143    /// variable.
144    ///
145    /// # Panics
146    ///
147    /// This function will panic if `set` has not previously been called.
148    ///
149    /// # Examples
150    ///
151    /// ```no_run
152    /// #[macro_use]
153    /// extern crate scoped_tls;
154    ///
155    /// scoped_thread_local!(static FOO: u32);
156    ///
157    /// # fn main() {
158    /// FOO.with(|slot| {
159    ///     // work with `slot`
160    /// # drop(slot);
161    /// });
162    /// # }
163    /// ```
164    pub fn with<F, R>(&'static self, f: F) -> R
165        where F: FnOnce(&T) -> R
166    {
167        let val = self.inner.with(|c| c.get());
168        assert!(!val.is_null(), "cannot access a scoped thread local \
169                                 variable without calling `set` first");
170        unsafe {
171            f(&*(val as *const T))
172        }
173    }
174
175    /// Test whether this TLS key has been `set` for the current thread.
176    pub fn is_set(&'static self) -> bool {
177        self.inner.with(|c| !c.get().is_null())
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use std::cell::Cell;
184    use std::sync::mpsc::{channel, Sender};
185    use std::thread;
186
187    scoped_thread_local!(static FOO: u32);
188
189    #[test]
190    fn smoke() {
191        scoped_thread_local!(static BAR: u32);
192
193        assert!(!BAR.is_set());
194        BAR.set(&1, || {
195            assert!(BAR.is_set());
196            BAR.with(|slot| {
197                assert_eq!(*slot, 1);
198            });
199        });
200        assert!(!BAR.is_set());
201    }
202
203    #[test]
204    fn cell_allowed() {
205        scoped_thread_local!(static BAR: Cell<u32>);
206
207        BAR.set(&Cell::new(1), || {
208            BAR.with(|slot| {
209                assert_eq!(slot.get(), 1);
210            });
211        });
212    }
213
214    #[test]
215    fn scope_item_allowed() {
216        assert!(!FOO.is_set());
217        FOO.set(&1, || {
218            assert!(FOO.is_set());
219            FOO.with(|slot| {
220                assert_eq!(*slot, 1);
221            });
222        });
223        assert!(!FOO.is_set());
224    }
225
226    #[test]
227    fn panic_resets() {
228        struct Check(Sender<u32>);
229        impl Drop for Check {
230            fn drop(&mut self) {
231                FOO.with(|r| {
232                    self.0.send(*r).unwrap();
233                })
234            }
235        }
236
237        let (tx, rx) = channel();
238        let t = thread::spawn(|| {
239            FOO.set(&1, || {
240                let _r = Check(tx);
241
242                FOO.set(&2, || {
243                    panic!()
244                });
245            });
246        });
247
248        assert_eq!(rx.recv().unwrap(), 1);
249        assert!(t.join().is_err());
250    }
251
252    #[test]
253    fn attrs_allowed() {
254        scoped_thread_local!(
255            /// Docs
256            static BAZ: u32
257        );
258
259        scoped_thread_local!(
260            #[allow(non_upper_case_globals)]
261            static quux: u32
262        );
263
264        let _ = BAZ;
265        let _ = quux;
266    }
267}