Skip to content

Commit 4a97533

Browse files
authored
Merge pull request #3184 from m-ou-se/thread-local-cell-methods
Thread local Cell methods.
2 parents 69833de + b766399 commit 4a97533

File tree

1 file changed

+399
-0
lines changed

1 file changed

+399
-0
lines changed
Lines changed: 399 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,399 @@
1+
- Feature Name: thread_local_cell_methods
2+
- Start Date: 2021-10-17
3+
- RFC PR: [rust-lang/rfcs#3184](https://github.com/rust-lang/rfcs/pull/3184)
4+
- Rust Issue: [rust-lang/rust#92122](https://github.com/rust-lang/rust/issues/92122)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Adding methods to `LocalKey` for `LocalKey<Cell<T>>` and `LocalKey<RefCell<T>>` to make thread local cells easier to use.
10+
11+
# Motivation
12+
[motivation]: #motivation
13+
14+
Almost all real-world usages of `thread_local! {}` involve a `Cell` or `RefCell`.
15+
Using the resulting `LocalKey` from a `thread_local! {}` declaration gets verbose due to having to use `.with(|_| ..)`.
16+
(For context: `.with()` is necessary because there's no correct lifetime for the thread local value.
17+
This method makes sure that any borrows end before the thread ends.)
18+
19+
```rust
20+
thread_local! {
21+
static THINGS: RefCell<Vec<i32>> = RefCell::new(Vec::new());
22+
}
23+
24+
fn f() {
25+
THINGS.with(|things| things.borrow_mut().push(1));
26+
27+
// ...
28+
29+
THINGS.with(|things| {
30+
let things = things.borrow();
31+
println!("{:?}", things);
32+
});
33+
}
34+
```
35+
36+
In addition, using `.set()` on a thread local cell through `.with()` results in unnecessary initialization,
37+
since `.with` will trigger the lazy initialization, even though `.set()` will overwrite the value directly afterwards:
38+
39+
```rust
40+
thread_local! {
41+
static ID: Cell<usize> = Cell::new(generate_id());
42+
}
43+
44+
fn f() {
45+
ID.with(|id| id.set(1)); // Ends up calling generate_id() the first time, while ignoring its result.
46+
47+
// ...
48+
}
49+
```
50+
51+
# Proposed additions
52+
53+
We add `.set()`, `.get()`\*, `.take()` and `.replace()` on `LocalKey<Cell<T>>` and `LocalKey<RefCell<T>>` such that they can used directly without using `.with()`:
54+
55+
(\* `.get()` only for `Cell`, not for `RefCell`.)
56+
57+
```rust
58+
thread_local! {
59+
static THINGS: RefCell<Vec<i32>> = RefCell::new(Vec::new());
60+
}
61+
62+
fn f() {
63+
THINGS.set(vec![1, 2, 3]);
64+
65+
// ...
66+
67+
let v: Vec<i32> = THINGS.take();
68+
}
69+
```
70+
71+
For `.set()`, this *skips the initialization expression*:
72+
73+
```rust
74+
thread_local! {
75+
static ID: Cell<usize> = panic!("This thread doesn't have an ID yet!");
76+
}
77+
78+
fn f() {
79+
// ID.with(|id| ..) at this point would panic.
80+
81+
ID.set(123); // This does *not* result in a panic.
82+
}
83+
```
84+
85+
In addition, we add `.with_ref` and `.with_mut` for `LocalKey<RefCell<T>>` to do `.with()` and `.borrow()` or `.borrow_mut()` at once:
86+
87+
```rust
88+
thread_local! {
89+
static THINGS: RefCell<Vec<i32>> = RefCell::new(Vec::new());
90+
}
91+
92+
fn f() {
93+
THINGS.with_mut(|v| v.push(1));
94+
95+
// ...
96+
97+
let len = THINGS.with_ref(|v| v.len());
98+
}
99+
```
100+
101+
# Full reference of the proposed additions
102+
103+
```rust
104+
impl<T: 'static> LocalKey<Cell<T>> {
105+
/// Sets or initializes the contained value.
106+
///
107+
/// Unlike the other methods, this will *not* run the lazy initializer of
108+
/// the thread local. Instead, it will be directly initialized with the
109+
/// given value if it wasn't initialized yet.
110+
///
111+
/// # Panics
112+
///
113+
/// Panics if the key currently has its destructor running,
114+
/// and it **may** panic if the destructor has previously been run for this thread.
115+
///
116+
/// # Examples
117+
///
118+
/// ```
119+
/// use std::cell::Cell;
120+
///
121+
/// thread_local! {
122+
/// static X: Cell<i32> = panic!("!");
123+
/// }
124+
///
125+
/// // Calling X.get() here would result in a panic.
126+
///
127+
/// X.set(123); // But X.set() is fine, as it skips the initializer above.
128+
///
129+
/// assert_eq!(X.get(), 123);
130+
/// ```
131+
pub fn set(&'static self, value: T);
132+
133+
/// Returns a copy of the contained value.
134+
///
135+
/// This will lazily initialize the value if this thread has not referenced
136+
/// this key yet.
137+
///
138+
/// # Panics
139+
///
140+
/// Panics if the key currently has its destructor running,
141+
/// and it **may** panic if the destructor has previously been run for this thread.
142+
///
143+
/// # Examples
144+
///
145+
/// ```
146+
/// use std::cell::Cell;
147+
///
148+
/// thread_local! {
149+
/// static X: Cell<i32> = Cell::new(1);
150+
/// }
151+
///
152+
/// assert_eq!(X.get(), 1);
153+
/// ```
154+
pub fn get(&'static self) -> T where T: Copy;
155+
156+
/// Takes the contained value, leaving `Default::default()` in its place.
157+
///
158+
/// This will lazily initialize the value if this thread has not referenced
159+
/// this key yet.
160+
///
161+
/// # Panics
162+
///
163+
/// Panics if the key currently has its destructor running,
164+
/// and it **may** panic if the destructor has previously been run for this thread.
165+
///
166+
/// # Examples
167+
///
168+
/// ```
169+
/// use std::cell::Cell;
170+
///
171+
/// thread_local! {
172+
/// static X: Cell<Option<i32>> = Cell::new(Some(1));
173+
/// }
174+
///
175+
/// assert_eq!(X.take(), Some(1));
176+
/// assert_eq!(X.take(), None);
177+
/// ```
178+
pub fn take(&'static self) -> T where T: Default;
179+
180+
/// Replaces the contained value, returning the old value.
181+
///
182+
/// This will lazily initialize the value if this thread has not referenced
183+
/// this key yet.
184+
///
185+
/// # Panics
186+
///
187+
/// Panics if the key currently has its destructor running,
188+
/// and it **may** panic if the destructor has previously been run for this thread.
189+
///
190+
/// # Examples
191+
///
192+
/// ```
193+
/// use std::cell::Cell;
194+
///
195+
/// thread_local! {
196+
/// static X: Cell<i32> = Cell::new(1);
197+
/// }
198+
///
199+
/// assert_eq!(X.replace(2), 1);
200+
/// assert_eq!(X.replace(3), 2);
201+
/// ```
202+
pub fn replace(&'static self, value: T) -> T;
203+
}
204+
```
205+
206+
```rust
207+
impl<T: 'static> LocalKey<RefCell<T>> {
208+
/// Acquires a reference to the contained value.
209+
///
210+
/// This will lazily initialize the value if this thread has not referenced
211+
/// this key yet.
212+
///
213+
/// # Panics
214+
///
215+
/// Panics if the value is currently borrowed.
216+
///
217+
/// Panics if the key currently has its destructor running,
218+
/// and it **may** panic if the destructor has previously been run for this thread.
219+
///
220+
/// # Example
221+
///
222+
/// ```
223+
/// use std::cell::RefCell;
224+
///
225+
/// thread_local! {
226+
/// static X: RefCell<Vec<i32>> = RefCell::new(Vec::new());
227+
/// }
228+
///
229+
/// X.with_ref(|v| assert!(v.is_empty()));
230+
/// ```
231+
pub fn with_ref<F, R>(&'static self, f: F) -> R where F: FnOnce(&T) -> R;
232+
233+
/// Acquires a mutable reference to the contained value.
234+
///
235+
/// This will lazily initialize the value if this thread has not referenced
236+
/// this key yet.
237+
///
238+
/// # Panics
239+
///
240+
/// Panics if the value is currently borrowed.
241+
///
242+
/// Panics if the key currently has its destructor running,
243+
/// and it **may** panic if the destructor has previously been run for this thread.
244+
///
245+
/// # Example
246+
///
247+
/// ```
248+
/// use std::cell::RefCell;
249+
///
250+
/// thread_local! {
251+
/// static X: RefCell<Vec<i32>> = RefCell::new(Vec::new());
252+
/// }
253+
///
254+
/// X.with_mut(|v| v.push(1));
255+
///
256+
/// X.with_ref(|v| assert_eq!(*v, vec![1]));
257+
/// ```
258+
pub fn with_mut<F, R>(&'static self, f: F) -> R where F: FnOnce(&mut T) -> R;
259+
260+
/// Sets or initializes the contained value.
261+
///
262+
/// Unlike the other methods, this will *not* run the lazy initializer of
263+
/// the thread local. Instead, it will be directly initialized with the
264+
/// given value if it wasn't initialized yet.
265+
///
266+
/// # Panics
267+
///
268+
/// Panics if the key currently has its destructor running,
269+
/// and it **may** panic if the destructor has previously been run for this thread.
270+
///
271+
/// # Examples
272+
///
273+
/// ```
274+
/// use std::cell::RefCell;
275+
///
276+
/// thread_local! {
277+
/// static X: RefCell<Vec<i32>> = panic!("!");
278+
/// }
279+
///
280+
/// // Calling X.with() here would result in a panic.
281+
///
282+
/// X.set(vec![1, 2, 3]); // But X.set() is fine, as it skips the initializer above.
283+
///
284+
/// X.with_ref(|v| assert_eq!(*v, vec![1, 2, 3]));
285+
/// ```
286+
pub fn set(&'static self, value: T);
287+
288+
/// Takes the contained value, leaving `Default::default()` in its place.
289+
///
290+
/// This will lazily initialize the value if this thread has not referenced
291+
/// this key yet.
292+
///
293+
/// # Panics
294+
///
295+
/// Panics if the value is currently borrowed.
296+
///
297+
/// Panics if the key currently has its destructor running,
298+
/// and it **may** panic if the destructor has previously been run for this thread.
299+
///
300+
/// # Examples
301+
///
302+
/// ```
303+
/// use std::cell::RefCell;
304+
///
305+
/// thread_local! {
306+
/// static X: RefCell<Vec<i32>> = RefCell::new(Vec::new());
307+
/// }
308+
///
309+
/// X.with_mut(|v| v.push(1));
310+
///
311+
/// let a = X.take();
312+
///
313+
/// assert_eq!(a, vec![1]);
314+
///
315+
/// X.with_ref(|v| assert!(v.is_empty()));
316+
/// ```
317+
pub fn take(&'static self) -> T where T: Default;
318+
319+
/// Replaces the contained value, returning the old value.
320+
///
321+
/// # Panics
322+
///
323+
/// Panics if the value is currently borrowed.
324+
///
325+
/// Panics if the key currently has its destructor running,
326+
/// and it **may** panic if the destructor has previously been run for this thread.
327+
///
328+
/// # Examples
329+
///
330+
/// ```
331+
/// use std::cell::RefCell;
332+
///
333+
/// thread_local! {
334+
/// static X: RefCell<Vec<i32>> = RefCell::new(Vec::new());
335+
/// }
336+
///
337+
/// let prev = X.replace(vec![1, 2, 3]);
338+
/// assert!(prev.is_empty());
339+
///
340+
/// X.with_ref(|v| assert_eq!(*v, vec![1, 2, 3]));
341+
/// ```
342+
pub fn replace(&'static self, value: T) -> T;
343+
}
344+
```
345+
346+
# Drawbacks
347+
[drawbacks]: #drawbacks
348+
349+
- We can no longer use the method names `set`, `get`, etc. on `LocalKey<T>` (if `T` can include `Cell` or `RefCell`).
350+
351+
- It might encourage code that's less efficient on some platforms.
352+
A single `THREAD_LOCAL.with(|x| ..)` is more efficient than using multiple `.set()` and `.get()` (etc.),
353+
since it needs to look up the thread local address every time, which is not free on all platforms.
354+
355+
# Alternatives
356+
357+
Alternatives for making it easier to work with thread local cells:
358+
359+
- Don't do anything, and keep wrapping everything in `.with(|x| ..)`.
360+
361+
- Somehow invent and implement the `'thread` or `'caller` lifetime, removing the need for `.with(|x| ..)`.
362+
363+
- Add `THREAD_LOCAL.borrow()` and `THREAD_LOCAL.borrow_mut()`, just like `RefCell` has.
364+
365+
This wouldn't be sound.
366+
One could move the returned proxy object into a thread local that outlives this thread local.
367+
(Or just `Box::leak()` it.)
368+
369+
Alternatives for avoiding the initializer:
370+
371+
- Add a `LocalKey<T>::try_initialize` method.
372+
373+
- This will be bit more complicated to implement efficiently.
374+
(A `LocalKey` just contains a single function pointer to the thread-local-address-getter, which is often optimized out.
375+
This doesn't play nice with being generic over the initialization function.)
376+
377+
- Thread locals with a `const` initializer (currently unstable, but likely stabilized soon) do not have the concept of being 'uninitialized' and do not run any lazy initialization.
378+
With `.set()` for `LocalKey<Cell<T>>`, that doesn't make a difference, as overwriting the const-initialized value has the same effect.
379+
However, for the generic `LocalKey<T>` we cannot allow changes without internal mutability,
380+
meaning that we can allow initialization (like `.try_initialize()`),
381+
but not changing it later (like `.set()`).
382+
Since a `const` initialized thread local does not know whether its value has been observed yet,
383+
we can't do anything other than implement `.try_initialize()` by always failing or panicking.
384+
385+
- Even if this function existed, it would still be nice to have a simple `THREAD_LOCAL.set(..)`.
386+
387+
# Prior art
388+
[prior-art]: #prior-art
389+
390+
- [`scoped-tls`](https://docs.rs/scoped-tls/1.0.0/scoped_tls/struct.ScopedKey.html)
391+
provides 'scoped thread locals' which must be `.set()` before using them. (They will panic otherwise.)
392+
393+
# Unresolved questions
394+
[unresolved-questions]: #unresolved-questions
395+
396+
- Should we use the names `with_borrow` and `with_borrow_mut` instead of `with_ref` and `with_mut`, to match `RefCell`'s method names?
397+
- Do we also want anything for `UnsafeCell`? Maybe `LocalKey<UnsafeCell<T>>::get()` to get the `*mut T`, just like `UnsafeCell<T>::get()`.
398+
- Are there any other types commonly used as thread locals for which we should do something similar?
399+
- Should `.set` skip the initializer, or not? We should consider this question again at stabilization time, and we should listen for anyone reporting concerns here (especially if it caused semantically unexpected behavior).

0 commit comments

Comments
 (0)