thread_scoped_ref/lib.rs
1//! # Thread scoped reference
2//!
3//! A library that is similar to a thread local storage but allows to store references / dyn
4//! Trait within a scope.
5//!
6//! It's similar to `std::thread_local` but allows you to store non-static references. Since the
7//! reference is non-static, the value has to be scoped (the reference MUST NOT escape the scope).
8//! It also works with dynamic dispatch (e.g. `dyn Trait`). Scopes can be nested. Everything is
9//! thread-local.
10//!
11//! # Example
12//!
13//! Short example:
14//!
15//! ```
16//! use thread_scoped_ref::{thread_scoped_ref, scoped, with};
17//! use std::collections::HashMap;
18//!
19//! thread_scoped_ref!(SOME_ENV_VALUES, HashMap<String, String>);
20//!
21//! /// It's not possible to pass `&HashMap<String, String>` to this function since it's called
22//! /// by some library you don't control...
23//! fn read_env_value() {
24//! // ... so we read from the static 'SOME_ENV_VALUES'.
25//! with(&SOME_ENV_VALUES, |maybe_env_values| {
26//! // don't "unwrap" in reality: Since `maybe_env_values` will be `None` if not
27//! // called within a scope!
28//! let env_values = maybe_env_values.unwrap();
29//! assert_eq!("true", env_values.get("delete_entire_ssd").unwrap());
30//! });
31//! }
32//!
33//! /// An external library you don't control or generated code.
34//! fn external_library(function_ptr : fn()) {
35//! function_ptr();
36//! }
37//!
38//! let mut env_values = HashMap::default();
39//! env_values.insert("delete_entire_ssd".to_string(), "true".to_string());
40//! // Create a scope. Note: We only need a reference to `env_values` (no move required).
41//! scoped(&SOME_ENV_VALUES, &env_values, || {
42//! external_library(read_env_value);
43//! });
44//! ```
45//!
46//! Long example:
47//!
48//! ```
49//! use thread_scoped_ref::{thread_scoped_ref, scoped, with};
50//!
51//! /// Declare the `REF_TO_A_STRING`.
52//! thread_scoped_ref!(REF_TO_A_STRING, str);
53//!
54//! /// This function reads the value and prints the value.
55//! fn value_consumer() {
56//! with(&REF_TO_A_STRING, |maybe_string| {
57//! // `maybe_string` is `Some` if this is called within a scope, or `None` if not called
58//! // within a scope.
59//! if let Some(string) = maybe_string {
60//! println!("String is: '{}'", string);
61//! } else {
62//! println!("There's no string.");
63//! }
64//! });
65//! }
66//!
67//! // Example #1: prints `There's no string` (since not called within a scope).
68//! value_consumer();
69//!
70//! // Example #2: With a scope.
71//! let my_string = "The String!".to_string();
72//! // note: We use the reference and not the actual string. It's not static!
73//! let my_string_ref = &my_string;
74//! scoped(&REF_TO_A_STRING, my_string_ref, || {
75//! // prints `String is: 'The String!'`
76//! value_consumer();
77//! });
78//!
79//! // Example #3: Nested scopes.
80//! let another_string = "Another string".to_string();
81//! scoped(&REF_TO_A_STRING, &another_string, || {
82//! // prints `String is: 'Another string'`
83//! value_consumer();
84//! // a nested scope.
85//! scoped(&REF_TO_A_STRING, my_string_ref, || {
86//! // prints `String is: 'The String!'`
87//! value_consumer();
88//! });
89//! // prints `String is: 'Another string'`
90//! value_consumer();
91//! });
92//!
93//! // Example #4: No scope (like example 1). prints `There's no string`.
94//! value_consumer();
95//! ```
96//!
97//! # Use case
98//!
99//! It's useful if you need to 'inject' some sort of context into a function you provide that gets
100//! called by a library you don't control. One example is Serde: You can write custom
101//! serialize/deserialize methods but it's not possible to call them with custom data (a context) -
102//! unless you also write the serialization/deserialization of the container manually (not by using
103//! Serde derive).
104//!
105//! Something like this can be achieved with thread scoped references. See the Serde demo
106//! test for details.
107
108mod helper;
109mod scope;
110
111pub use helper::*;
112pub use scope::Scope;
113
114/// A shortcut macro for `thread_local! { static IDENTIFIER : Scope<Type> = Scope::default() }`.
115///
116/// # See also
117///
118/// * [`with`]
119/// * [`scoped`]
120///
121/// # Examples
122///
123/// With a struct:
124///
125/// ```
126/// use thread_scoped_ref::{thread_scoped_ref, scoped, with};
127///
128/// struct MyStruct(String);
129///
130/// thread_scoped_ref!(MY_STRUCT, MyStruct);
131///
132/// // use it:
133/// let demo_struct = MyStruct("Hello".to_string());
134///
135/// scoped(&MY_STRUCT, &demo_struct, || {
136/// with(&MY_STRUCT, |maybe_my_struct_ref| {
137/// assert_eq!("Hello", maybe_my_struct_ref.unwrap().0);
138/// })
139/// })
140/// ```
141///
142/// With a trait / dynamic dispatch (note the `dyn`):
143///
144/// ```
145/// use thread_scoped_ref::{thread_scoped_ref, scoped, with};
146///
147/// trait MyTrait {
148/// fn string(&self) -> &str;
149/// }
150///
151/// struct StructImplementingMyTrait(String);
152///
153/// impl MyTrait for StructImplementingMyTrait {
154/// fn string(&self) -> &str {
155/// &self.0
156/// }
157/// }
158///
159/// thread_scoped_ref!(MY_TRAIT, dyn MyTrait);
160///
161/// // use it:
162/// let my_struct = StructImplementingMyTrait("Hello World".to_string());
163///
164/// scoped(&MY_TRAIT, &my_struct, || {
165/// with(&MY_TRAIT, |maybe_my_trait_ref| {
166/// assert_eq!("Hello World", maybe_my_trait_ref.unwrap().string());
167/// })
168/// })
169/// ```
170///
171/// [`with`]: fn.with.html
172/// [`scoped`]: fn.scoped.html
173#[macro_export]
174macro_rules! thread_scoped_ref {
175 ($identifier:ident, $typ:ty) => {
176 std::thread_local! {
177 static $identifier: $crate::Scope<$typ> = $crate::Scope::default();
178 }
179 };
180}