1use std::{cell::UnsafeCell, marker::PhantomData, mem::MaybeUninit};
9
10#[derive(Debug)]
11pub enum Infallible {}
12
13impl std::fmt::Display for Infallible {
14 fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 unreachable!("Infallible is unconstructible");
16 }
17}
18
19impl std::error::Error for Infallible {}
20
21#[derive(Debug)]
58pub struct Format<T>(pub T);
59
60impl<T> std::fmt::Display for Format<T>
61where
62 T: std::error::Error,
63{
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 const LIMIT: usize = 256;
68
69 struct Source<'a>(Option<&'a (dyn std::error::Error + 'static)>);
72 impl<'a> Iterator for Source<'a> {
73 type Item = &'a (dyn std::error::Error + 'static);
74 fn next(&mut self) -> Option<Self::Item> {
75 let current = self.0;
76 self.0 = match current {
77 Some(current) => current.source(),
78 None => None,
79 };
80 current
81 }
82 }
83
84 write!(f, "{}", self.0)?;
85 let mut itr = Source(self.0.source());
86 for source in itr.by_ref().take(LIMIT) {
87 write!(f, "\n caused by: {}", source)?;
88 }
89
90 if itr.next().is_some() {
91 write!(f, "\n ... (limit reached)")?;
92 }
93
94 Ok(())
95 }
96}
97
98pub fn format<E>(err: &E) -> String
102where
103 E: std::error::Error + ?Sized,
104{
105 Format(err).to_string()
106}
107
108#[repr(C)]
130pub struct InlineError<const N: usize = 16> {
131 vtable: &'static ErrorVTable,
133
134 object: UnsafeCell<[MaybeUninit<u8>; N]>,
140}
141
142unsafe impl<const N: usize> Send for InlineError<N> {}
144
145unsafe impl<const N: usize> Sync for InlineError<N> {}
147
148impl<const N: usize> InlineError<N> {
149 pub fn new<T>(error: T) -> Self
158 where
159 T: std::error::Error + Send + Sync + 'static,
160 {
161 const { assert!(std::mem::size_of::<T>() <= N, "error type is too big") };
162 const {
163 assert!(
164 std::mem::align_of::<T>() <= std::mem::align_of::<&'static ErrorVTable>(),
165 "error type has alignment stricter than 8"
166 )
167 };
168
169 let mut this = Self {
170 vtable: &ErrorVTable {
171 debug: error_debug::<T>,
172 display: error_display::<T>,
173 source: error_source::<T>,
174 drop: error_drop::<T>,
175 },
176 object: UnsafeCell::new([MaybeUninit::uninit(); N]),
177 };
178
179 unsafe { this.object.get_mut().as_mut_ptr().cast::<T>().write(error) };
185
186 this
187 }
188
189 fn ptr_ref(&self) -> Ref<'_> {
193 Ref {
194 ptr: self.object.get().cast::<MaybeUninit<u8>>(),
195 _lifetime: PhantomData,
196 }
197 }
198}
199
200impl<const N: usize> Drop for InlineError<N> {
201 fn drop(&mut self) {
202 unsafe { (self.vtable.drop)(self.object.get().cast::<MaybeUninit<u8>>()) }
209 }
210}
211
212impl<const N: usize> std::fmt::Display for InlineError<N> {
213 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214 unsafe { (self.vtable.display)(self.object.get().cast::<MaybeUninit<u8>>(), f) }
217 }
218}
219
220impl<const N: usize> std::fmt::Debug for InlineError<N> {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 write!(f, "InlineError<{}> {{ object: ", N)?;
223 unsafe { (self.vtable.debug)(self.object.get().cast::<MaybeUninit<u8>>(), f) }?;
226 write!(f, ", vtable: {:?} }}", self.vtable)
227 }
228}
229
230impl<const N: usize> std::error::Error for InlineError<N> {
231 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
232 unsafe { (self.vtable.source)(self.ptr_ref()) }
235 }
236}
237
238#[derive(Debug)]
239struct ErrorVTable {
240 debug: unsafe fn(*const MaybeUninit<u8>, &mut std::fmt::Formatter<'_>) -> std::fmt::Result,
241 display: unsafe fn(*const MaybeUninit<u8>, &mut std::fmt::Formatter<'_>) -> std::fmt::Result,
242 source: unsafe fn(Ref<'_>) -> Option<&(dyn std::error::Error + 'static)>,
243 drop: unsafe fn(*mut MaybeUninit<u8>),
244}
245
246unsafe fn error_debug<T>(
248 object: *const MaybeUninit<u8>,
249 f: &mut std::fmt::Formatter<'_>,
250) -> std::fmt::Result
251where
252 T: std::fmt::Debug,
253{
254 unsafe { &*object.cast::<T>() }.fmt(f)
256}
257
258unsafe fn error_display<T>(
260 object: *const MaybeUninit<u8>,
261 f: &mut std::fmt::Formatter<'_>,
262) -> std::fmt::Result
263where
264 T: std::fmt::Display,
265{
266 unsafe { &*object.cast::<T>() }.fmt(f)
268}
269
270unsafe fn error_source<T>(object: Ref<'_>) -> Option<&(dyn std::error::Error + 'static)>
274where
275 T: std::error::Error + 'static,
276{
277 unsafe { &*object.ptr.cast::<T>() }.source()
279}
280
281struct Ref<'a> {
283 ptr: *const MaybeUninit<u8>,
284 _lifetime: PhantomData<&'a MaybeUninit<u8>>,
285}
286
287unsafe fn error_drop<T>(object: *mut MaybeUninit<u8>) {
290 unsafe { std::ptr::drop_in_place::<T>(object.cast::<T>()) }
292}
293
294#[cfg(test)]
299mod tests {
300 use std::sync::{
301 Arc, Mutex,
302 atomic::{AtomicUsize, Ordering},
303 };
304
305 use thiserror::Error;
306
307 use super::*;
308
309 #[derive(Error, Debug, Clone)]
310 #[error("error A")]
311 struct ErrorA;
312
313 #[derive(Error, Debug, Clone)]
314 #[error("error B with val {val}")]
315 struct ErrorB<Inner: std::error::Error> {
316 val: usize,
317 #[source]
318 source: Inner,
319 }
320
321 #[derive(Error, Debug)]
322 #[error("error C with message {message}")]
323 struct ErrorC<Inner: std::error::Error> {
324 message: String,
325 source: Inner,
327 }
328
329 #[test]
330 fn test_formatting() {
331 let message = format(&ErrorA);
333 assert_eq!(message, "error A");
334
335 assert_eq!(Format(ErrorA).to_string(), "error A");
336 assert_eq!(Format(&ErrorA).to_string(), "error A");
337
338 let error = ErrorB {
340 val: 10,
341 source: ErrorA,
342 };
343
344 let expected = "error B with val 10\n caused by: error A";
345 assert_eq!(format(&error), expected);
346 assert_eq!(Format(&error).to_string(), expected);
347 assert_eq!(Format(error.clone()).to_string(), expected);
348
349 let error = ErrorC {
351 message: "Hello World".to_string(),
352 source: error,
353 };
354 let expected = "error C with message Hello World\n \
355 caused by: error B with val 10\n \
356 caused by: error A";
357
358 assert_eq!(format(&error), expected);
359 assert_eq!(Format(&error).to_string(), expected);
360 assert_eq!(Format(error).to_string(), expected);
361 }
362
363 #[derive(Debug)]
366 struct Infinite;
367
368 impl std::fmt::Display for Infinite {
369 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
370 f.write_str("an unending source")
371 }
372 }
373
374 impl std::error::Error for Infinite {
375 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
376 Some(self)
377 }
378 }
379
380 #[test]
381 fn test_infinite_detected() {
382 let s = Format(Infinite).to_string();
385 assert!(s.contains("(limit reached)"));
386 }
387
388 #[derive(Debug, Error)]
393 #[error("zero sized error")]
394 struct ZeroSizedError;
395
396 #[derive(Debug, Error)]
397 #[error("error with drop: {}", self.0.load(Ordering::Relaxed))]
398 struct ErrorWithDrop(Arc<AtomicUsize>);
399
400 impl Drop for ErrorWithDrop {
401 fn drop(&mut self) {
402 self.0.fetch_add(1, Ordering::Relaxed);
403 }
404 }
405
406 #[derive(Debug, Error)]
407 #[error("error with source")]
408 struct ErrorWithSource(#[from] ZeroSizedError);
409
410 struct ErrorWithInteriorMutability(Mutex<usize>);
412
413 impl std::fmt::Debug for ErrorWithInteriorMutability {
414 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415 let current = {
416 let mut guard = self.0.lock().unwrap();
417 let current = *guard;
418 *guard += 1;
419 current
420 };
421
422 write!(f, "{}", current)
423 }
424 }
425
426 impl std::fmt::Display for ErrorWithInteriorMutability {
427 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428 let current = {
429 let mut guard = self.0.lock().unwrap();
430 let current = *guard;
431 *guard += 1;
432 current
433 };
434
435 write!(f, "{}", current)
436 }
437 }
438
439 impl std::error::Error for ErrorWithInteriorMutability {
440 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
441 *self.0.lock().unwrap() += 1;
442 None
443 }
444 }
445
446 #[test]
447 fn sizes_and_offsets() {
448 let ref_size = std::mem::size_of::<&'static ()>();
449 let ref_align = std::mem::align_of::<&'static ()>();
450
451 assert_eq!(std::mem::offset_of!(InlineError<0>, object), ref_size);
452 assert_eq!(std::mem::offset_of!(InlineError<8>, object), ref_size);
453 assert_eq!(std::mem::offset_of!(InlineError<16>, object), ref_size);
454
455 assert_eq!(std::mem::size_of::<InlineError<0>>(), ref_size);
456 assert_eq!(std::mem::size_of::<Option<InlineError<0>>>(), ref_size);
457 assert_eq!(std::mem::align_of::<InlineError<0>>(), ref_align);
458 assert_eq!(std::mem::align_of::<Option<InlineError<0>>>(), ref_align);
459
460 assert_eq!(std::mem::size_of::<InlineError<8>>(), ref_size + 8);
461 assert_eq!(std::mem::size_of::<Option<InlineError<8>>>(), ref_size + 8);
462 assert_eq!(std::mem::align_of::<InlineError<8>>(), ref_align);
463 assert_eq!(std::mem::align_of::<Option<InlineError<8>>>(), ref_align);
464
465 assert_eq!(std::mem::size_of::<InlineError<16>>(), ref_size + 16);
466 assert_eq!(
467 std::mem::size_of::<Option<InlineError<16>>>(),
468 ref_size + 16
469 );
470 assert_eq!(std::mem::align_of::<InlineError<16>>(), ref_align);
471 assert_eq!(std::mem::align_of::<Option<InlineError<16>>>(), ref_align);
472 }
473
474 #[test]
475 fn inline_error_zst() {
476 use std::error::Error;
477
478 let error = InlineError::<0>::new(ZeroSizedError);
479 assert_eq!(
480 std::mem::size_of_val(&error),
481 8,
482 "expected 8 bytes for the payload and 0-bytes for the vtable"
483 );
484 assert_eq!(error.to_string(), "zero sized error");
485
486 let debug = format!("{:?}", error);
487 assert!(
488 debug.starts_with(&format!("InlineError<0> {{ object: {:?}", ZeroSizedError)),
489 "debug message: {}",
490 debug
491 );
492
493 assert!(error.source().is_none());
494
495 let _ = Box::new(error);
497 }
498
499 #[test]
500 fn inline_error_with_drop() {
501 use std::error::Error;
502
503 let count = Arc::new(AtomicUsize::new(10));
504 let mut error = InlineError::<8>::new(ErrorWithDrop(count.clone()));
505 assert_eq!(
506 std::mem::size_of_val(&error),
507 16,
508 "expected 8 bytes for the payload and 8-bytes for the vtable"
509 );
510 assert_eq!(error.to_string(), "error with drop: 10");
511 assert!(error.source().is_none());
512
513 error = InlineError::new(ZeroSizedError);
515 assert_eq!(error.to_string(), "zero sized error");
516
517 assert_eq!(count.load(Ordering::Relaxed), 11, "failed to run \"drop\"");
518 }
519
520 #[test]
521 fn inline_error_with_interior_mutability() {
522 use std::error::Error;
523
524 let error = InlineError::<64>::new(ErrorWithInteriorMutability(Mutex::new(0)));
526 assert_eq!(
527 std::mem::size_of_val(&error),
528 72,
529 "expected 64 bytes for the payload and 8-bytes for the vtable"
530 );
531 assert_eq!(error.to_string(), "0");
532 let debug = format!("{:?}", error);
533 assert!(debug.contains("object: 1"), "got {}", debug);
534 assert_eq!(error.to_string(), "2");
535
536 let debug = format!("{:?}", error);
537 assert!(debug.contains("object: 3"), "got {}", debug);
538
539 assert!(error.source().is_none());
540 assert_eq!(error.to_string(), "5");
541 }
542
543 #[test]
544 fn inline_error_with_source() {
545 use std::error::Error;
546
547 let error = InlineError::<8>::new(ErrorWithSource(ZeroSizedError));
548 assert_eq!(
549 std::mem::size_of_val(&error),
550 16,
551 "expected 8 bytes for the payload and 8-bytes for the vtable"
552 );
553 assert_eq!(error.to_string(), "error with source");
554 assert_eq!(error.source().unwrap().to_string(), "zero sized error");
555
556 let _ = Box::new(error);
558 }
559}