use std::borrow::Cow; /// Provides context furthering the explanation of *how* you got to an error. #[derive(Debug, Clone)] pub struct Context(ContextInner); impl std::fmt::Display for Context { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.0 { ContextInner::String(ref s) => f.write_str(s), ContextInner::Compound(ref ctxs) => { let mut ctxs = ctxs.iter(); if let Some(ctx) = ctxs.next() { write!(f, "{ctx}")?; for ctx in ctxs { write!(f, "\n{ctx}")?; } } Ok(()) } } } } #[derive(Debug, Clone)] enum ContextInner { String(Cow<'static, str>), Compound(Vec), } pub trait IntoContext { fn into_context(self) -> Context; #[inline(always)] fn chain(self, other: impl IntoContext) -> Context where Self: Sized, { self.into_context().chain(other) } } impl IntoContext for Context { #[inline(always)] fn into_context(self) -> Context { self } /// Chains another piece of context that is equal from a hierarchical perspective. // TODO: should this inline? Would the compiler be allowed to fold the allocations into a // single one with inlining? fn chain(self, other: impl IntoContext) -> Self { let items = match self { Context(ContextInner::Compound(mut items)) => { items.push(other.into_context()); items } _ => vec![self, other.into_context()], }; Context(ContextInner::Compound(items)) } } impl IntoContext for String { #[inline] fn into_context(self) -> Context { Context(ContextInner::String(self.into())) } } impl IntoContext for &'static str { #[inline] fn into_context(self) -> Context { Context(ContextInner::String(self.into())) } } impl IntoContext for Cow<'static, str> { // TODO: should this always inline? #[inline] fn into_context(self) -> Context { Context(ContextInner::String(self)) } } impl IntoContext for F where C: IntoContext, F: FnOnce() -> C, { #[inline(always)] fn into_context(self) -> Context { self().into_context() } }