1use std::{cell::UnsafeCell, marker::PhantomData, mem::MaybeUninit};
9
10pub fn format<E>(err: &E) -> String
14where
15 E: std::error::Error + ?Sized,
16{
17 struct SourceIterator<'a>(Option<&'a (dyn std::error::Error + 'static)>);
20 impl<'a> Iterator for SourceIterator<'a> {
21 type Item = &'a (dyn std::error::Error + 'static);
22 fn next(&mut self) -> Option<Self::Item> {
23 let current = self.0;
24 self.0 = match current {
25 Some(current) => current.source(),
26 None => None,
27 };
28 current
29 }
30 }
31
32 let mut message = err.to_string();
34 for source in SourceIterator(err.source()) {
36 message.push_str("\n caused by: ");
37 message.push_str(&source.to_string());
38 }
39 message
40}
41
42#[repr(C)]
64pub struct InlineError<const N: usize = 16> {
65 vtable: &'static ErrorVTable,
67
68 object: UnsafeCell<[MaybeUninit<u8>; N]>,
74}
75
76unsafe impl<const N: usize> Send for InlineError<N> {}
78
79unsafe impl<const N: usize> Sync for InlineError<N> {}
81
82impl<const N: usize> InlineError<N> {
83 pub fn new<T>(error: T) -> Self
92 where
93 T: std::error::Error + Send + Sync + 'static,
94 {
95 const { assert!(std::mem::size_of::<T>() <= N, "error type is too big") };
96 const {
97 assert!(
98 std::mem::align_of::<T>() <= std::mem::align_of::<&'static ErrorVTable>(),
99 "error type has alignment stricter than 8"
100 )
101 };
102
103 let mut this = Self {
104 vtable: &ErrorVTable {
105 debug: error_debug::<T>,
106 display: error_display::<T>,
107 source: error_source::<T>,
108 drop: error_drop::<T>,
109 },
110 object: UnsafeCell::new([MaybeUninit::uninit(); N]),
111 };
112
113 unsafe { this.object.get_mut().as_mut_ptr().cast::<T>().write(error) };
119
120 this
121 }
122
123 fn ptr_ref(&self) -> Ref<'_> {
127 Ref {
128 ptr: self.object.get().cast::<MaybeUninit<u8>>(),
129 _lifetime: PhantomData,
130 }
131 }
132}
133
134impl<const N: usize> Drop for InlineError<N> {
135 fn drop(&mut self) {
136 unsafe { (self.vtable.drop)(self.object.get().cast::<MaybeUninit<u8>>()) }
143 }
144}
145
146impl<const N: usize> std::fmt::Display for InlineError<N> {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 unsafe { (self.vtable.display)(self.object.get().cast::<MaybeUninit<u8>>(), f) }
151 }
152}
153
154impl<const N: usize> std::fmt::Debug for InlineError<N> {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 write!(f, "InlineError<{}> {{ object: ", N)?;
157 unsafe { (self.vtable.debug)(self.object.get().cast::<MaybeUninit<u8>>(), f) }?;
160 write!(f, ", vtable: {:?} }}", self.vtable)
161 }
162}
163
164impl<const N: usize> std::error::Error for InlineError<N> {
165 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
166 unsafe { (self.vtable.source)(self.ptr_ref()) }
169 }
170}
171
172#[derive(Debug)]
173struct ErrorVTable {
174 debug: unsafe fn(*const MaybeUninit<u8>, &mut std::fmt::Formatter<'_>) -> std::fmt::Result,
175 display: unsafe fn(*const MaybeUninit<u8>, &mut std::fmt::Formatter<'_>) -> std::fmt::Result,
176 source: unsafe fn(Ref<'_>) -> Option<&(dyn std::error::Error + 'static)>,
177 drop: unsafe fn(*mut MaybeUninit<u8>),
178}
179
180unsafe fn error_debug<T>(
182 object: *const MaybeUninit<u8>,
183 f: &mut std::fmt::Formatter<'_>,
184) -> std::fmt::Result
185where
186 T: std::fmt::Debug,
187{
188 unsafe { &*object.cast::<T>() }.fmt(f)
190}
191
192unsafe fn error_display<T>(
194 object: *const MaybeUninit<u8>,
195 f: &mut std::fmt::Formatter<'_>,
196) -> std::fmt::Result
197where
198 T: std::fmt::Display,
199{
200 unsafe { &*object.cast::<T>() }.fmt(f)
202}
203
204unsafe fn error_source<T>(object: Ref<'_>) -> Option<&(dyn std::error::Error + 'static)>
208where
209 T: std::error::Error + 'static,
210{
211 unsafe { &*object.ptr.cast::<T>() }.source()
213}
214
215struct Ref<'a> {
217 ptr: *const MaybeUninit<u8>,
218 _lifetime: PhantomData<&'a MaybeUninit<u8>>,
219}
220
221unsafe fn error_drop<T>(object: *mut MaybeUninit<u8>) {
224 unsafe { std::ptr::drop_in_place::<T>(object.cast::<T>()) }
226}
227
228#[cfg(test)]
233mod tests {
234 use std::sync::{
235 atomic::{AtomicUsize, Ordering},
236 Arc, Mutex,
237 };
238
239 use thiserror::Error;
240
241 use super::*;
242
243 #[derive(Error, Debug)]
244 #[error("error A")]
245 struct ErrorA;
246
247 #[derive(Error, Debug)]
248 #[error("error B with val {val}")]
249 struct ErrorB<Inner: std::error::Error> {
250 val: usize,
251 #[source]
252 source: Inner,
253 }
254
255 #[derive(Error, Debug)]
256 #[error("error C with message {message}")]
257 struct ErrorC<Inner: std::error::Error> {
258 message: String,
259 source: Inner,
261 }
262
263 #[test]
264 fn test_formatting() {
265 let message = format(&ErrorA);
267 assert_eq!(message, "error A");
268
269 let error = ErrorB {
271 val: 10,
272 source: ErrorA,
273 };
274
275 let expected = "error B with val 10\n caused by: error A";
276 assert_eq!(format(&error), expected);
277
278 let error = ErrorC {
280 message: "Hello World".to_string(),
281 source: error,
282 };
283 let expected = "error C with message Hello World\n \
284 caused by: error B with val 10\n \
285 caused by: error A";
286 assert_eq!(format(&error), expected);
287 }
288
289 #[derive(Debug, Error)]
294 #[error("zero sized error")]
295 struct ZeroSizedError;
296
297 #[derive(Debug, Error)]
298 #[error("error with drop: {}", self.0.load(Ordering::Relaxed))]
299 struct ErrorWithDrop(Arc<AtomicUsize>);
300
301 impl Drop for ErrorWithDrop {
302 fn drop(&mut self) {
303 self.0.fetch_add(1, Ordering::Relaxed);
304 }
305 }
306
307 #[derive(Debug, Error)]
308 #[error("error with source")]
309 struct ErrorWithSource(#[from] ZeroSizedError);
310
311 struct ErrorWithInteriorMutability(Mutex<usize>);
313
314 impl std::fmt::Debug for ErrorWithInteriorMutability {
315 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316 let current = {
317 let mut guard = self.0.lock().unwrap();
318 let current = *guard;
319 *guard += 1;
320 current
321 };
322
323 write!(f, "{}", current)
324 }
325 }
326
327 impl std::fmt::Display for ErrorWithInteriorMutability {
328 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329 let current = {
330 let mut guard = self.0.lock().unwrap();
331 let current = *guard;
332 *guard += 1;
333 current
334 };
335
336 write!(f, "{}", current)
337 }
338 }
339
340 impl std::error::Error for ErrorWithInteriorMutability {
341 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
342 *self.0.lock().unwrap() += 1;
343 None
344 }
345 }
346
347 #[test]
348 fn sizes_and_offsets() {
349 let ref_size = std::mem::size_of::<&'static ()>();
350 let ref_align = std::mem::align_of::<&'static ()>();
351
352 assert_eq!(std::mem::offset_of!(InlineError<0>, object), ref_size);
353 assert_eq!(std::mem::offset_of!(InlineError<8>, object), ref_size);
354 assert_eq!(std::mem::offset_of!(InlineError<16>, object), ref_size);
355
356 assert_eq!(std::mem::size_of::<InlineError<0>>(), ref_size);
357 assert_eq!(std::mem::size_of::<Option<InlineError<0>>>(), ref_size);
358 assert_eq!(std::mem::align_of::<InlineError<0>>(), ref_align);
359 assert_eq!(std::mem::align_of::<Option<InlineError<0>>>(), ref_align);
360
361 assert_eq!(std::mem::size_of::<InlineError<8>>(), ref_size + 8);
362 assert_eq!(std::mem::size_of::<Option<InlineError<8>>>(), ref_size + 8);
363 assert_eq!(std::mem::align_of::<InlineError<8>>(), ref_align);
364 assert_eq!(std::mem::align_of::<Option<InlineError<8>>>(), ref_align);
365
366 assert_eq!(std::mem::size_of::<InlineError<16>>(), ref_size + 16);
367 assert_eq!(
368 std::mem::size_of::<Option<InlineError<16>>>(),
369 ref_size + 16
370 );
371 assert_eq!(std::mem::align_of::<InlineError<16>>(), ref_align);
372 assert_eq!(std::mem::align_of::<Option<InlineError<16>>>(), ref_align);
373 }
374
375 #[test]
376 fn inline_error_zst() {
377 use std::error::Error;
378
379 let error = InlineError::<0>::new(ZeroSizedError);
380 assert_eq!(
381 std::mem::size_of_val(&error),
382 8,
383 "expected 8 bytes for the payload and 0-bytes for the vtable"
384 );
385 assert_eq!(error.to_string(), "zero sized error");
386
387 let debug = format!("{:?}", error);
388 assert!(
389 debug.starts_with(&format!("InlineError<0> {{ object: {:?}", ZeroSizedError)),
390 "debug message: {}",
391 debug
392 );
393
394 assert!(error.source().is_none());
395
396 let _ = Box::new(error);
398 }
399
400 #[test]
401 fn inline_error_with_drop() {
402 use std::error::Error;
403
404 let count = Arc::new(AtomicUsize::new(10));
405 let mut error = InlineError::<8>::new(ErrorWithDrop(count.clone()));
406 assert_eq!(
407 std::mem::size_of_val(&error),
408 16,
409 "expected 8 bytes for the payload and 8-bytes for the vtable"
410 );
411 assert_eq!(error.to_string(), "error with drop: 10");
412 assert!(error.source().is_none());
413
414 error = InlineError::new(ZeroSizedError);
416 assert_eq!(error.to_string(), "zero sized error");
417
418 assert_eq!(count.load(Ordering::Relaxed), 11, "failed to run \"drop\"");
419 }
420
421 #[test]
422 fn inline_error_with_interior_mutability() {
423 use std::error::Error;
424
425 let error = InlineError::<16>::new(ErrorWithInteriorMutability(Mutex::new(0)));
426 assert_eq!(
427 std::mem::size_of_val(&error),
428 24,
429 "expected 16 bytes for the payload and 8-bytes for the vtable"
430 );
431 assert_eq!(error.to_string(), "0");
432 let debug = format!("{:?}", error);
433 assert!(debug.contains("object: 1"), "got {}", debug);
434 assert_eq!(error.to_string(), "2");
435
436 let debug = format!("{:?}", error);
437 assert!(debug.contains("object: 3"), "got {}", debug);
438
439 assert!(error.source().is_none());
440 assert_eq!(error.to_string(), "5");
441 }
442
443 #[test]
444 fn inline_error_with_source() {
445 use std::error::Error;
446
447 let error = InlineError::<8>::new(ErrorWithSource(ZeroSizedError));
448 assert_eq!(
449 std::mem::size_of_val(&error),
450 16,
451 "expected 8 bytes for the payload and 8-bytes for the vtable"
452 );
453 assert_eq!(error.to_string(), "error with source");
454 assert_eq!(error.source().unwrap().to_string(), "zero sized error");
455
456 let _ = Box::new(error);
458 }
459}