Skip to content

Commit fc5bfba

Browse files
committed
Use MaybeUninit on rustc >= 1.36.0
1 parent 1be15df commit fc5bfba

File tree

3 files changed

+107
-11
lines changed

3 files changed

+107
-11
lines changed

build.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use std::error::Error;
2+
use std::ffi::OsString;
3+
use std::process::{exit, Command};
4+
5+
fn main() {
6+
let rustc = std::env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"));
7+
let command = format!("`{} --version`", rustc.to_string_lossy());
8+
9+
let output = Command::new(&rustc)
10+
.arg("--version")
11+
.output()
12+
.unwrap_or_else(|e| {
13+
eprintln!("Error: failed to run {}: {}", command, e);
14+
exit(1)
15+
});
16+
17+
let supports_maybe_uninit = parse(&output.stdout).unwrap_or_else(|e| {
18+
eprintln!("Error: failed to parse output of {}: {}", command, e);
19+
exit(1)
20+
});
21+
22+
if supports_maybe_uninit {
23+
println!("cargo:rustc-cfg=supports_maybe_uninit");
24+
}
25+
}
26+
27+
fn parse(output: &[u8]) -> Result<bool, Box<dyn Error>> {
28+
let s = std::str::from_utf8(output)?;
29+
let last_line = s.lines().last().unwrap_or(s);
30+
let mut words = last_line.trim().split(' ');
31+
if words.next() != Some("rustc") {
32+
return Err("version does not start with 'rustc'".into());
33+
}
34+
let mut triplet = words
35+
.next()
36+
.ok_or("output does not contain version triplet")?
37+
.split('.');
38+
if triplet.next() != Some("1") {
39+
return Err("rustc major version is not 1".into());
40+
}
41+
let minor: u32 = triplet
42+
.next()
43+
.ok_or("rustc version does not contain minor version")?
44+
.parse()
45+
.map_err(|e| format!("failed to parse minor version: {}", e))?;
46+
47+
Ok(minor >= 36)
48+
}

src/lib.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,14 @@
7070
extern crate lazy_static;
7171

7272
mod cached;
73+
mod maybe_uninit;
7374
mod thread_id;
7475
mod unreachable;
7576

7677
#[allow(deprecated)]
7778
pub use cached::{CachedIntoIter, CachedIterMut, CachedThreadLocal};
7879

80+
use maybe_uninit::MaybeUninit;
7981
use std::cell::UnsafeCell;
8082
use std::fmt;
8183
use std::iter::FusedIterator;
@@ -86,7 +88,7 @@ use std::ptr;
8688
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
8789
use std::sync::Mutex;
8890
use thread_id::Thread;
89-
use unreachable::{UncheckedOptionExt, UncheckedResultExt};
91+
use unreachable::UncheckedResultExt;
9092

9193
// Use usize::BITS once it has stabilized and the MSRV has been bumped.
9294
#[cfg(target_pointer_width = "16")]
@@ -119,8 +121,7 @@ pub struct ThreadLocal<T: Send> {
119121

120122
struct Entry<T> {
121123
present: AtomicBool,
122-
// Use MaybeUninit once the MSRV has been bumped.
123-
value: UnsafeCell<Option<T>>,
124+
value: UnsafeCell<MaybeUninit<T>>,
124125
}
125126

126127
// ThreadLocal is always Sync, even if T isn't
@@ -226,7 +227,15 @@ impl<T: Send> ThreadLocal<T> {
226227
if bucket_ptr.is_null() {
227228
return None;
228229
}
229-
unsafe { (&*(*bucket_ptr.add(thread.index)).value.get()).as_ref() }
230+
unsafe {
231+
let entry = &*bucket_ptr.add(thread.index);
232+
// Read without atomic operations as only this thread can set the value.
233+
if (&entry.present as *const _ as *const bool).read() {
234+
Some(&*(&*entry.value.get()).as_ptr())
235+
} else {
236+
None
237+
}
238+
}
230239
}
231240

232241
#[cold]
@@ -251,12 +260,12 @@ impl<T: Send> ThreadLocal<T> {
251260
// Insert the new element into the bucket
252261
let entry = unsafe { &*bucket_ptr.add(thread.index) };
253262
let value_ptr = entry.value.get();
254-
unsafe { value_ptr.write(Some(data)) };
263+
unsafe { value_ptr.write(MaybeUninit::new(data)) };
255264
entry.present.store(true, Ordering::Release);
256265

257266
self.values.fetch_add(1, Ordering::Release);
258267

259-
unsafe { (&*value_ptr).as_ref().unchecked_unwrap() }
268+
unsafe { &*(&*value_ptr).as_ptr() }
260269
}
261270

262271
/// Returns an iterator over the local values of all threads in unspecified
@@ -370,7 +379,7 @@ impl<'a, T: Send + Sync> Iterator for Iter<'a, T> {
370379
self.index += 1;
371380
if entry.present.load(Ordering::Acquire) {
372381
self.yielded += 1;
373-
return Some(unsafe { (&*entry.value.get()).as_ref().unchecked_unwrap() });
382+
return Some(unsafe { &*(&*entry.value.get()).as_ptr() });
374383
}
375384
}
376385
}
@@ -401,7 +410,7 @@ struct RawIterMut<T: Send> {
401410
}
402411

403412
impl<T: Send> Iterator for RawIterMut<T> {
404-
type Item = *mut Option<T>;
413+
type Item = *mut MaybeUninit<T>;
405414

406415
fn next(&mut self) -> Option<Self::Item> {
407416
if self.remaining == 0 {
@@ -448,7 +457,7 @@ impl<'a, T: Send + 'a> Iterator for IterMut<'a, T> {
448457
fn next(&mut self) -> Option<&'a mut T> {
449458
self.raw
450459
.next()
451-
.map(|x| unsafe { &mut *(*x).as_mut().unchecked_unwrap() })
460+
.map(|x| unsafe { &mut *(&mut *x).as_mut_ptr() })
452461
}
453462

454463
fn size_hint(&self) -> (usize, Option<usize>) {
@@ -470,7 +479,7 @@ impl<T: Send> Iterator for IntoIter<T> {
470479
fn next(&mut self) -> Option<T> {
471480
self.raw
472481
.next()
473-
.map(|x| unsafe { (*x).take().unchecked_unwrap() })
482+
.map(|x| unsafe { std::mem::replace(&mut *x, MaybeUninit::uninit()).assume_init() })
474483
}
475484

476485
fn size_hint(&self) -> (usize, Option<usize>) {
@@ -485,7 +494,7 @@ fn allocate_bucket<T>(size: usize) -> *mut Entry<T> {
485494
(0..size)
486495
.map(|_| Entry::<T> {
487496
present: AtomicBool::new(false),
488-
value: UnsafeCell::new(None),
497+
value: UnsafeCell::new(MaybeUninit::uninit()),
489498
})
490499
.collect::<Vec<_>>()
491500
.into_boxed_slice(),

src/maybe_uninit.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#[cfg(supports_maybe_uninit)]
2+
pub(crate) use std::mem::MaybeUninit;
3+
4+
#[cfg(not(supports_maybe_uninit))]
5+
mod polyfill {
6+
use std::mem::ManuallyDrop;
7+
use std::ptr;
8+
9+
use unreachable::UncheckedOptionExt;
10+
11+
/// A simple `Option`-based implementation of `MaybeUninit` for compiler versions < 1.36.0.
12+
pub struct MaybeUninit<T>(Option<ManuallyDrop<T>>);
13+
14+
impl<T> MaybeUninit<T> {
15+
pub fn new(val: T) -> Self {
16+
MaybeUninit(Some(ManuallyDrop::new(val)))
17+
}
18+
pub fn uninit() -> Self {
19+
MaybeUninit(None)
20+
}
21+
pub fn as_ptr(&self) -> *const T {
22+
self.0
23+
.as_ref()
24+
.map(|v| &**v as *const _)
25+
.unwrap_or_else(ptr::null)
26+
}
27+
pub fn as_mut_ptr(&mut self) -> *mut T {
28+
self.0
29+
.as_mut()
30+
.map(|v| &mut **v as *mut _)
31+
.unwrap_or_else(ptr::null_mut)
32+
}
33+
pub unsafe fn assume_init(self) -> T {
34+
ManuallyDrop::into_inner(self.0.unchecked_unwrap())
35+
}
36+
}
37+
}
38+
#[cfg(not(supports_maybe_uninit))]
39+
pub(crate) use self::polyfill::MaybeUninit;

0 commit comments

Comments
 (0)