Skip to content

Commit f015e6f

Browse files
committed
core: optimize LazyCell size
1 parent 8a7ca93 commit f015e6f

File tree

1 file changed

+59
-11
lines changed

1 file changed

+59
-11
lines changed

library/core/src/cell/lazy.rs

+59-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
use crate::cell::{Cell, OnceCell};
2-
use crate::fmt;
31
use crate::ops::Deref;
2+
use crate::{fmt, mem};
3+
4+
use super::UnsafeCell;
5+
6+
enum State<T, F> {
7+
Uninit(F),
8+
Init(T),
9+
Poisoned,
10+
}
411

512
/// A value which is initialized on the first access.
613
///
@@ -31,8 +38,7 @@ use crate::ops::Deref;
3138
/// ```
3239
#[unstable(feature = "lazy_cell", issue = "109736")]
3340
pub struct LazyCell<T, F = fn() -> T> {
34-
cell: OnceCell<T>,
35-
init: Cell<Option<F>>,
41+
state: UnsafeCell<State<T, F>>,
3642
}
3743

3844
impl<T, F: FnOnce() -> T> LazyCell<T, F> {
@@ -53,8 +59,8 @@ impl<T, F: FnOnce() -> T> LazyCell<T, F> {
5359
/// ```
5460
#[inline]
5561
#[unstable(feature = "lazy_cell", issue = "109736")]
56-
pub const fn new(init: F) -> LazyCell<T, F> {
57-
LazyCell { cell: OnceCell::new(), init: Cell::new(Some(init)) }
62+
pub const fn new(f: F) -> LazyCell<T, F> {
63+
LazyCell { state: UnsafeCell::new(State::Uninit(f)) }
5864
}
5965

6066
/// Forces the evaluation of this lazy value and returns a reference to
@@ -77,10 +83,47 @@ impl<T, F: FnOnce() -> T> LazyCell<T, F> {
7783
#[inline]
7884
#[unstable(feature = "lazy_cell", issue = "109736")]
7985
pub fn force(this: &LazyCell<T, F>) -> &T {
80-
this.cell.get_or_init(|| match this.init.take() {
81-
Some(f) => f(),
82-
None => panic!("`Lazy` instance has previously been poisoned"),
83-
})
86+
let state = unsafe { &*this.state.get() };
87+
match state {
88+
State::Init(data) => data,
89+
State::Uninit(_) => unsafe { LazyCell::really_init(this) },
90+
State::Poisoned => panic!("LazyCell has previously been poisoned"),
91+
}
92+
}
93+
94+
/// # Safety
95+
/// May only be called when the state is `Uninit`.
96+
#[cold]
97+
unsafe fn really_init(this: &LazyCell<T, F>) -> &T {
98+
let state = unsafe { &mut *this.state.get() };
99+
// Temporarily mark the state as poisoned. This prevents reentrant
100+
// accesses and correctly poisons the cell if the closure panicked.
101+
let State::Uninit(f) = mem::replace(state, State::Poisoned) else { unreachable!() };
102+
103+
let data = f();
104+
105+
// If the closure accessed the cell, the mutable borrow will be
106+
// invalidated, so create a new one here.
107+
let state = unsafe { &mut *this.state.get() };
108+
*state = State::Init(data);
109+
110+
// A reference obtained by downcasting from the mutable borrow
111+
// would become stale if other references are created in `force`.
112+
// Borrow the state directly instead.
113+
let state = unsafe { &*this.state.get() };
114+
let State::Init(data) = state else { unreachable!() };
115+
data
116+
}
117+
}
118+
119+
impl<T, F> LazyCell<T, F> {
120+
#[inline]
121+
fn get(&self) -> Option<&T> {
122+
let state = unsafe { &*self.state.get() };
123+
match state {
124+
State::Init(data) => Some(data),
125+
_ => None,
126+
}
84127
}
85128
}
86129

@@ -105,6 +148,11 @@ impl<T: Default> Default for LazyCell<T> {
105148
#[unstable(feature = "lazy_cell", issue = "109736")]
106149
impl<T: fmt::Debug, F> fmt::Debug for LazyCell<T, F> {
107150
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108-
f.debug_struct("Lazy").field("cell", &self.cell).field("init", &"..").finish()
151+
let mut d = f.debug_tuple("LazyCell");
152+
match self.get() {
153+
Some(data) => d.field(data),
154+
None => d.field(&format_args!("<uninit>")),
155+
};
156+
d.finish()
109157
}
110158
}

0 commit comments

Comments
 (0)