Skip to content

Commit 7eec09c

Browse files
authored
Merge pull request #750 from lilizoey/refactor/global-safety
Rework the safety invariants of `Global` a bit
2 parents 529654c + 50831ab commit 7eec09c

File tree

1 file changed

+61
-52
lines changed

1 file changed

+61
-52
lines changed

godot-ffi/src/global.rs

+61-52
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use std::ops::{Deref, DerefMut};
98
use std::sync::{Mutex, MutexGuard, TryLockError};
109

1110
/// Ergonomic global variables.
@@ -66,7 +65,6 @@ impl<T> Global<T> {
6665
let guard = Self::ensure_init(mutex_guard, true)
6766
.unwrap_or_else(|| panic!("previous Global<T> initialization failed due to panic"));
6867

69-
debug_assert!(matches!(*guard.mutex_guard, InitState::Initialized(_)));
7068
guard
7169
}
7270

@@ -79,45 +77,31 @@ impl<T> Global<T> {
7977
}
8078
Err(TryLockError::Poisoned(poisoned)) => {
8179
return Err(GlobalLockError::Poisoned {
82-
circumvent: GlobalGuard {
83-
mutex_guard: poisoned.into_inner(),
84-
},
80+
// We can likely use `new_unchecked` here, but verifying that it's safe would need somewhat tricky reasoning.
81+
// Since this error condition isn't very common, it is likely not very important to optimize access to the value here.
82+
// Especially since most users will likely not want to access it anyway.
83+
circumvent: GlobalGuard::new(poisoned.into_inner())
84+
.expect("Poisoned global guard should always be initialized"),
8585
});
8686
}
8787
};
8888

89-
match guard {
90-
None => Err(GlobalLockError::InitFailed),
91-
Some(guard) => {
92-
debug_assert!(matches!(*guard.mutex_guard, InitState::Initialized(_)));
93-
Ok(guard)
94-
}
95-
}
89+
guard.ok_or(GlobalLockError::InitFailed)
9690
}
9791

9892
fn ensure_init(
9993
mut mutex_guard: MutexGuard<'_, InitState<T>>,
10094
may_panic: bool,
10195
) -> Option<GlobalGuard<'_, T>> {
102-
let pending_state = match &mut *mutex_guard {
96+
let init_fn = match &mut *mutex_guard {
10397
InitState::Initialized(_) => {
104-
return Some(GlobalGuard { mutex_guard });
105-
}
106-
InitState::TransientInitializing => {
107-
// SAFETY: only set inside this function and all paths (panic + return) leave the enum in a different state.
108-
unsafe { std::hint::unreachable_unchecked() };
98+
// SAFETY: `mutex_guard` is `Initialized`.
99+
return Some(unsafe { GlobalGuard::new_unchecked(mutex_guard) });
109100
}
110101
InitState::Failed => {
111102
return None;
112103
}
113-
state @ InitState::Pending(_) => {
114-
std::mem::replace(state, InitState::TransientInitializing)
115-
}
116-
};
117-
118-
let InitState::Pending(init_fn) = pending_state else {
119-
// SAFETY: all other paths leave the function, see above.
120-
unsafe { std::hint::unreachable_unchecked() }
104+
InitState::Pending(init_fn) => init_fn,
121105
};
122106

123107
// Unwinding should be safe here, as there is no unsafe code relying on it.
@@ -137,32 +121,64 @@ impl<T> Global<T> {
137121
}
138122
};
139123

140-
Some(GlobalGuard { mutex_guard })
124+
// SAFETY: `mutex_guard` was either set to `Initialized` above, or we returned from the function.
125+
Some(unsafe { GlobalGuard::new_unchecked(mutex_guard) })
141126
}
142127
}
143128

144129
// ----------------------------------------------------------------------------------------------------------------------------------------------
145130
// Guards
146131

147-
/// Guard that temporarily gives access to a `Global<T>`'s inner value.
148-
pub struct GlobalGuard<'a, T> {
149-
mutex_guard: MutexGuard<'a, InitState<T>>,
150-
}
132+
// Encapsulate private fields.
133+
mod global_guard {
134+
use std::ops::{Deref, DerefMut};
135+
use std::sync::MutexGuard;
151136

152-
impl<T> Deref for GlobalGuard<'_, T> {
153-
type Target = T;
137+
use super::InitState;
154138

155-
fn deref(&self) -> &Self::Target {
156-
self.mutex_guard.unwrap_ref()
139+
/// Guard that temporarily gives access to a `Global<T>`'s inner value.
140+
pub struct GlobalGuard<'a, T> {
141+
// Safety invariant: Is `Initialized`.
142+
mutex_guard: MutexGuard<'a, InitState<T>>,
157143
}
158-
}
159144

160-
impl<T> DerefMut for GlobalGuard<'_, T> {
161-
fn deref_mut(&mut self) -> &mut Self::Target {
162-
self.mutex_guard.unwrap_mut()
145+
impl<'a, T> GlobalGuard<'a, T> {
146+
pub(super) fn new(mutex_guard: MutexGuard<'a, InitState<T>>) -> Option<Self> {
147+
match &*mutex_guard {
148+
InitState::Initialized(_) => Some(Self { mutex_guard }),
149+
_ => None,
150+
}
151+
}
152+
153+
/// # Safety
154+
///
155+
/// The value must be `Initialized`.
156+
pub(super) unsafe fn new_unchecked(mutex_guard: MutexGuard<'a, InitState<T>>) -> Self {
157+
debug_assert!(matches!(*mutex_guard, InitState::Initialized(_)));
158+
159+
Self::new(mutex_guard).unwrap_unchecked()
160+
}
161+
}
162+
163+
impl<T> Deref for GlobalGuard<'_, T> {
164+
type Target = T;
165+
166+
fn deref(&self) -> &Self::Target {
167+
// SAFETY: `self` is `Initialized`.
168+
unsafe { self.mutex_guard.as_initialized().unwrap_unchecked() }
169+
}
170+
}
171+
172+
impl<T> DerefMut for GlobalGuard<'_, T> {
173+
fn deref_mut(&mut self) -> &mut Self::Target {
174+
// SAFETY: `self` is `Initialized`.
175+
unsafe { self.mutex_guard.as_initialized_mut().unwrap_unchecked() }
176+
}
163177
}
164178
}
165179

180+
pub use global_guard::GlobalGuard;
181+
166182
// ----------------------------------------------------------------------------------------------------------------------------------------------
167183
// Errors
168184

@@ -184,28 +200,21 @@ pub enum GlobalLockError<'a, T> {
184200
enum InitState<T> {
185201
Initialized(T),
186202
Pending(fn() -> T),
187-
TransientInitializing,
188203
Failed,
189204
}
190205

191206
impl<T> InitState<T> {
192-
fn unwrap_ref(&self) -> &T {
207+
fn as_initialized(&self) -> Option<&T> {
193208
match self {
194-
InitState::Initialized(t) => t,
195-
_ => {
196-
// SAFETY: This method is only called from a guard, which can only be obtained in Initialized state.
197-
unsafe { std::hint::unreachable_unchecked() }
198-
}
209+
InitState::Initialized(t) => Some(t),
210+
_ => None,
199211
}
200212
}
201213

202-
fn unwrap_mut(&mut self) -> &mut T {
214+
fn as_initialized_mut(&mut self) -> Option<&mut T> {
203215
match self {
204-
InitState::Initialized(t) => t,
205-
_ => {
206-
// SAFETY: This method is only called from a guard, which can only be obtained in Initialized state.
207-
unsafe { std::hint::unreachable_unchecked() }
208-
}
216+
InitState::Initialized(t) => Some(t),
217+
_ => None,
209218
}
210219
}
211220
}

0 commit comments

Comments
 (0)