1use std::{convert::Infallible, error::Error, sync::PoisonError};
2use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
3
4
5pub trait HandlePoisonResult {
28 type PoisonlessResult;
30
31 #[must_use]
36 fn ignore_poison(self) -> Self::PoisonlessResult;
37
38 fn panic_if_poison(self) -> Self::PoisonlessResult;
45}
46
47#[inline]
49fn prove_unreachable(poison: &PoisonError<Infallible>) -> ! {
50 #[expect(clippy::uninhabited_references, reason = "this function is not reachable")]
51 match *poison.get_ref() {}
52}
53
54
55pub type LockResult<T> = Result<T, LockError<T>>;
59pub type PoisonlessLockResult<T> = Result<T, LockError<Infallible>>;
61
62impl<T> HandlePoisonResult for LockResult<T> {
63 type PoisonlessResult = PoisonlessLockResult<T>;
64
65 #[inline]
70 fn ignore_poison(self) -> Self::PoisonlessResult {
71 match self.map_err(LockError::ignore_poison) {
72 Ok(t) => Ok(t),
73 Err(poisonless_result) => poisonless_result,
74 }
75 }
76
77 #[inline]
84 fn panic_if_poison(self) -> Self::PoisonlessResult {
85 self.map_err(LockError::panic_if_poison)
86 }
87}
88
89pub enum LockError<T> {
93 Poisoned(PoisonError<T>),
97 LockedByCurrentThread,
100}
101
102impl<T> LockError<T> {
103 #[inline]
111 pub fn ignore_poison(self) -> PoisonlessLockResult<T> {
112 match self {
113 Self::Poisoned(poison) => Ok(poison.into_inner()),
114 Self::LockedByCurrentThread => Err(LockError::LockedByCurrentThread),
115 }
116 }
117
118 #[inline]
125 #[must_use]
126 pub fn panic_if_poison(self) -> LockError<Infallible> {
127 match self {
128 #[expect(
129 clippy::panic,
130 reason = "library users will frequently want to panic on poison",
131 )]
132 Self::Poisoned(_) => panic!("LockError was poison"),
133 Self::LockedByCurrentThread => LockError::LockedByCurrentThread,
134 }
135 }
136}
137
138impl<T> From<PoisonError<T>> for LockError<T> {
139 #[inline]
140 fn from(poison: PoisonError<T>) -> Self {
141 Self::Poisoned(poison)
142 }
143}
144
145impl<T> Debug for LockError<T> {
146 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
147 match self {
148 Self::Poisoned(poison) => f.debug_tuple("Poisoned").field(&poison).finish(),
149 Self::LockedByCurrentThread => f.write_str("LockedByCurrentThread"),
150 }
151 }
152}
153
154impl<T> Display for LockError<T> {
155 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
156 match self {
157 Self::Poisoned(_) => write!(
158 f,
159 "LockError due to poison (another thread panicked)",
160 ),
161 Self::LockedByCurrentThread => write!(
162 f,
163 "Failed to acquire a lock, because the same thread was holding it",
164 ),
165 }
166 }
167}
168
169impl<T> Error for LockError<T> {}
170
171impl PartialEq for LockError<Infallible> {
172 #[inline]
173 fn eq(&self, _other: &Self) -> bool {
174 match self {
176 Self::LockedByCurrentThread => true,
177 Self::Poisoned(poison) => prove_unreachable(poison),
178 }
179 }
180}
181
182impl Eq for LockError<Infallible> {}
183
184
185pub type TryLockResult<T> = Result<T, TryLockError<T>>;
189pub type PoisonlessTryLockResult<T> = Result<T, TryLockError<Infallible>>;
191
192impl<T> HandlePoisonResult for TryLockResult<T> {
193 type PoisonlessResult = PoisonlessTryLockResult<T>;
194
195 #[inline]
200 fn ignore_poison(self) -> Self::PoisonlessResult {
201 match self.map_err(TryLockError::ignore_poison) {
202 Ok(t) => Ok(t),
203 Err(poisonless_result) => poisonless_result,
204 }
205 }
206
207 #[inline]
214 fn panic_if_poison(self) -> Self::PoisonlessResult {
215 self.map_err(TryLockError::panic_if_poison)
216 }
217}
218
219pub enum TryLockError<T> {
223 Poisoned(PoisonError<T>),
227 LockedByCurrentThread,
230 WouldBlock,
233}
234
235impl<T> TryLockError<T> {
236 #[inline]
245 pub fn ignore_poison(self) -> PoisonlessTryLockResult<T> {
246 match self {
247 Self::Poisoned(poison) => Ok(poison.into_inner()),
248 Self::LockedByCurrentThread => Err(TryLockError::LockedByCurrentThread),
249 Self::WouldBlock => Err(TryLockError::WouldBlock),
250 }
251 }
252
253 #[inline]
260 #[must_use]
261 pub fn panic_if_poison(self) -> TryLockError<Infallible> {
262 match self {
263 #[expect(
264 clippy::panic,
265 reason = "library users will frequently want to panic on poison",
266 )]
267 Self::Poisoned(_) => panic!("TryLockError was poison"),
268 Self::LockedByCurrentThread => TryLockError::LockedByCurrentThread,
269 Self::WouldBlock => TryLockError::WouldBlock,
270 }
271 }
272}
273
274impl<T> From<PoisonError<T>> for TryLockError<T> {
275 #[inline]
276 fn from(poison: PoisonError<T>) -> Self {
277 Self::Poisoned(poison)
278 }
279}
280
281impl<T> Debug for TryLockError<T> {
282 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
283 match self {
284 Self::Poisoned(poison) => f.debug_tuple("Poisoned").field(&poison).finish(),
285 Self::LockedByCurrentThread => f.write_str("LockedByCurrentThread"),
286 Self::WouldBlock => f.write_str("WouldBlock"),
287 }
288 }
289}
290
291impl<T> Display for TryLockError<T> {
292 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
293 match self {
294 Self::Poisoned(_) => write!(
295 f,
296 "TryLockError due to poison (another thread panicked)",
297 ),
298 Self::LockedByCurrentThread => write!(
299 f,
300 "Failed to acquire a lock, because the same thread was holding it",
301 ),
302 Self::WouldBlock => write!(
303 f,
304 "Lock was held by a different thread, so acquiring it would block",
305 ),
306 }
307 }
308}
309
310impl<T> Error for TryLockError<T> {}
311
312impl PartialEq for TryLockError<Infallible> {
313 #[inline]
314 fn eq(&self, other: &Self) -> bool {
315 match self {
316 Self::LockedByCurrentThread => matches!(other, Self::LockedByCurrentThread),
317 Self::WouldBlock => matches!(other, Self::WouldBlock),
318 Self::Poisoned(poison) => prove_unreachable(poison),
319 }
320 }
321}
322
323impl Eq for TryLockError<Infallible> {}
324
325
326pub type AccessResult<T> = Result<T, AccessError<T>>;
332pub type PoisonlessAccessResult<T> = Result<T, AccessError<Infallible>>;
336
337impl<T> HandlePoisonResult for AccessResult<T> {
338 type PoisonlessResult = PoisonlessAccessResult<T>;
339
340 #[inline]
347 fn ignore_poison(self) -> Self::PoisonlessResult {
348 match self.map_err(AccessError::ignore_poison) {
349 Ok(t) => Ok(t),
350 Err(poisonless_result) => poisonless_result,
351 }
352 }
353
354 #[inline]
364 fn panic_if_poison(self) -> Self::PoisonlessResult {
365 self.map_err(|err| AccessError::panic_if_poison(err))
366 }
367}
368
369pub struct AccessError<T> {
379 pub poison: PoisonError<T>,
381}
382
383impl<T> AccessError<T> {
384 #[expect(clippy::missing_errors_doc, reason = "the function is infallible")]
391 #[inline]
392 pub fn ignore_poison(self) -> PoisonlessAccessResult<T> {
393 Ok(self.poison.into_inner())
394 }
395
396 #[inline]
404 pub fn panic_if_poison(self) -> ! {
405 #![expect(
406 clippy::panic,
407 reason = "library users will frequently want to panic on poison",
408 )]
409 panic!("AccessError is poison")
410 }
411}
412
413impl<T> From<PoisonError<T>> for AccessError<T> {
414 #[inline]
415 fn from(poison: PoisonError<T>) -> Self {
416 Self { poison }
417 }
418}
419
420impl<T> Debug for AccessError<T> {
421 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
422 f.debug_struct("AccessError")
423 .field("poison", &self.poison)
424 .finish()
425 }
426}
427
428impl<T> Display for AccessError<T> {
429 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
430 write!(f, "AccessError due to poison (another thread panicked)")
431 }
432}
433
434impl<T> Error for AccessError<T> {}
435
436impl PartialEq for AccessError<Infallible> {
437 #[inline]
438 fn eq(&self, _other: &Self) -> bool {
439 prove_unreachable(&self.poison)
440 }
441}
442
443impl Eq for AccessError<Infallible> {}
444
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 #[test]
451 fn lock_ignore_poison() {
452 let res_o: LockResult<()> = Ok(());
454 assert!(matches!(res_o.ignore_poison(), Ok(())));
455
456 let res_e: LockResult<()> = Err(LockError::LockedByCurrentThread);
458 assert!(matches!(res_e.ignore_poison(), Err(LockError::LockedByCurrentThread)));
459
460 let res_p: LockResult<()> = Err(PoisonError::new(()).into());
462 assert!(matches!(res_p.ignore_poison(), Ok(())));
463 }
464
465 #[test]
466 fn lock_panic_if_poison() {
467 let res_o: LockResult<()> = Ok(());
469 assert!(matches!(res_o.panic_if_poison(), Ok(())));
470
471 let res_e: LockResult<()> = Err(LockError::LockedByCurrentThread);
473 assert!(matches!(res_e.panic_if_poison(), Err(LockError::LockedByCurrentThread)));
474 }
475
476 #[test]
477 #[should_panic = "LockError was poison"]
478 fn panicking_lock_panic_if_poison() {
479 let res_p: LockResult<()> = Err(PoisonError::new(()).into());
481 #[expect(
482 clippy::let_underscore_must_use,
483 clippy::let_underscore_untyped,
484 reason = "function never returns",
485 )]
486 let _ = res_p.panic_if_poison();
487 }
488
489 #[test]
490 fn try_lock_ignore_poison() {
491 let res_o: TryLockResult<()> = Ok(());
493 assert!(matches!(res_o.ignore_poison(), Ok(())));
494
495 let res_e: TryLockResult<()> = Err(TryLockError::LockedByCurrentThread);
497 assert!(matches!(res_e.ignore_poison(), Err(TryLockError::LockedByCurrentThread)));
498
499 let res_p: TryLockResult<()> = Err(PoisonError::new(()).into());
501 assert!(matches!(res_p.ignore_poison(), Ok(())));
502 }
503
504 #[test]
505 fn try_lock_panic_if_poison() {
506 let res_o: TryLockResult<()> = Ok(());
508 assert!(matches!(res_o.panic_if_poison(), Ok(())));
509
510 let res_e: TryLockResult<()> = Err(TryLockError::LockedByCurrentThread);
512 assert!(matches!(res_e.panic_if_poison(), Err(TryLockError::LockedByCurrentThread)));
513 }
514
515 #[test]
516 #[should_panic = "TryLockError was poison"]
517 fn panicking_try_lock_panic_if_poison() {
518 let res_p: TryLockResult<()> = Err(PoisonError::new(()).into());
520 #[expect(
521 clippy::let_underscore_must_use,
522 clippy::let_underscore_untyped,
523 reason = "function never returns",
524 )]
525 let _ = res_p.panic_if_poison();
526 }
527
528 #[test]
529 fn access_ignore_poison() {
530 let res_o: AccessResult<()> = Ok(());
532 assert!(matches!(res_o.ignore_poison(), Ok(())));
533
534 let res_p: AccessResult<()> = Err(PoisonError::new(()).into());
538 assert!(matches!(res_p.ignore_poison(), Ok(())));
539 }
540
541 #[test]
542 fn access_panic_if_poison() {
543 let res_o: AccessResult<()> = Ok(());
545 assert!(matches!(res_o.panic_if_poison(), Ok(())));
546
547 }
549
550 #[test]
551 #[should_panic = "AccessError is poison"]
552 fn panicking_access_panic_if_poison() {
553 let res_p: AccessResult<()> = Err(PoisonError::new(()).into());
555 #[expect(
556 clippy::let_underscore_must_use,
557 clippy::let_underscore_untyped,
558 reason = "function never returns",
559 )]
560 let _ = res_p.panic_if_poison();
561 }
562
563 fn test_eq_impl<E: Eq, const N: usize>(errors: &[E; N]) {
564 for (i, error) in errors.iter().enumerate() {
565 for (j, other) in errors.iter().enumerate() {
566 assert_eq!(i == j, error == other);
567 }
568 }
569 }
570
571 #[test]
572 fn eq_impls() {
573 test_eq_impl(&[
575 LockError::<Infallible>::LockedByCurrentThread,
576 ]);
577 test_eq_impl(&[
578 TryLockError::<Infallible>::LockedByCurrentThread,
579 TryLockError::<Infallible>::WouldBlock,
580 ]);
581 }
583}