maybe_debug/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3#![cfg_attr(not(feature = "std"), no_std)]
4#![cfg_attr(maybe_debug_nightly, feature(specialization, ptr_metadata))]
5#![allow(
6    incomplete_features, // specialization is incomplete...
7)]
8
9use core::marker::PhantomData;
10use core::fmt::{self, Debug, Formatter};
11use core::ffi::c_void;
12
13#[rustversion::nightly]
14const NIGHTLY: bool = true;
15
16#[rustversion::not(nightly)]
17const NIGHTLY: bool = false;
18
19#[rustversion::nightly]
20mod backend {
21    include!("nightly.rs");
22}
23
24#[rustversion::not(nightly)]
25mod backend {
26    include!("stable.rs");
27}
28
29/// A version of [`std::dbg!`](https://doc.rust-lang.org/std/macro.dbg.html) that works regardless
30/// of whether or not `T` implements `Debug`
31///
32/// This requires the standard library to be present.
33///
34/// This macro is also aliased as `dbg!`,
35/// so you may use a fully qualified `maybe_debug::dbg!()` if you so chose.
36///
37/// See also [maybe_debug] function.
38///
39/// ## Example
40/// ```
41/// use maybe_debug::maybe_dbg;
42/// let a = vec![5, 4, 8];
43/// assert_eq!(maybe_dbg!(a), vec![5, 4, 8]);
44/// let a = vec![2, 4, 7];
45/// // NOTE: Absolute path is useful for the 'dbg!' variant (and often clearer)
46/// let (a, b, c) = maybe_debug::dbg!(a, format!("foooz"), 5u32);
47/// 
48/// let (a, b, _c): (Vec<i32>, String, u32) = maybe_debug::dbg!(a, b, c);
49/// drop(a);
50/// drop(b);
51/// ```
52#[macro_export]
53macro_rules! maybe_dbg {
54    () => (std::dbg!());
55    ($val:expr $(,)?) => {
56        {
57            let val = $val;
58            std::dbg!($crate::maybe_debug(&val));
59            val
60        }
61    };
62    ($($val:expr),+ $(,)?) => {
63        ($($crate::maybe_dbg!($val)),+ ,)
64    };
65}
66
67pub use self::maybe_dbg as dbg;
68
69/// Attempt to cast the specified value into `&dyn Debug`,
70/// returning `None` if this fails.
71///
72/// This always returns `None` on the stable compiler.
73///
74/// Currently it is not possible to support casting unsized types.
75#[inline]
76pub fn cast_debug<'a, T: 'a>(val: &'a T) -> Option<&'a (dyn Debug + 'a)> {
77    <T as backend::MaybeDebug>::cast_debug(val)
78}
79
80/// Attempt to cast the specified value into `&dyn Debug`,
81/// falling back to a reasonable default on failure.
82/// 
83/// This unconditionally delegates to [MaybeDebug::fallback] on the stable compiler.
84#[inline]
85pub fn maybe_debug<T: ?Sized>(val: &T) -> MaybeDebug<'_> {
86    <T as backend::MaybeDebug>::maybe_debug(val)
87}
88
89/// Optional presense of [Debug] information (equivalent to `Option<&dyn Debug>`)
90///
91/// The main difference from the equivalent `Option`
92/// is that it prints a reasonable fallback (the type's name).
93/// 
94/// In other words `maybe_debug(NotDebug)` gives `"NotDebug { ... }`
95/// instead of just printing `None`.
96///
97/// You can *always* retrieve the original type name
98/// of the value, regardless of whether the `Debug` implementation
99/// is `Some` or `None`.
100///
101/// The type name is the same one given by [core::any::type_name].
102///
103/// The specific variants of this struct are considered an implementation detail.
104#[non_exhaustive]
105#[allow(missing_docs)]
106#[derive(Copy, Clone)]
107pub enum MaybeDebug<'a> {
108    #[doc(hidden)]
109    DynTrait {
110        type_name: &'static str,
111        val: &'a (dyn Debug + 'a)
112    },
113    #[doc(hidden)]
114    TypeName {
115        type_name: &'static str
116    },
117    #[doc(hidden)]
118    Slice(MaybeDebugSlice<'a>),
119    #[doc(hidden)]
120    Str(&'a str),
121}
122impl<'a> MaybeDebug<'a> {
123    /// Check if this type contians an underlying `Debug` implementation.
124    ///
125    /// Using the `Option<&dyn Debug>` analogy, this would be equivalent to `is_some`
126    #[inline]
127    pub fn has_debug_info(&self) -> bool {
128        match *self {
129            MaybeDebug::Str(_) | MaybeDebug::DynTrait { .. } => true,
130            MaybeDebug::TypeName { .. } => false,
131            MaybeDebug::Slice(ref s) => s.has_debug_info(),
132        }
133    }
134    /// Check if this type is using a fallback implementation
135    /// (based on the type name) (ie `!self.has_debug_info()`)
136    /// 
137    /// Note this returns `true` even for `MaybeDebug::fallback_slice`,
138    /// despite the fact that does include length information.
139    /// 
140    /// Using the `Option<&dyn Debug>` analogy, this would be equivalent
141    /// to `is_none`.
142    #[inline]
143    pub fn is_fallback(&self) -> bool {
144        !self.has_debug_info()
145    }
146    /// Check if this type is known to be a slice.
147    ///
148    /// This function has false negatives, but no false positives.
149    ///
150    /// For example, if `MaybeDebug::fallback` is used, then this function
151    /// will return `false` even if `fallback` is given a slice.
152    #[inline]
153    pub fn is_known_slice(&self) -> bool {
154        matches!(*self, MaybeDebug::Slice { .. })
155    }
156    /// If the original value was a slice,
157    /// return its length.
158    /// 
159    /// Returns `None` if the original value was not known to be a slice.
160    ///
161    /// Just like [MaybeDebug::is_known_slice],
162    /// this may have false negatives on whether or not something is a slice.
163    /// However, if it returns `Some`, then the length is guarenteed to be correct.
164    #[inline]
165    pub fn original_slice_len(&self) -> Option<usize> {
166        match *self {
167            MaybeDebug::Slice(ref s) => Some(s.original_len()),
168            _ => None
169        }
170    }
171    /// Return the underling type name,
172    /// as given by `core::any::type_name`
173    ///
174    /// This function always succeeds.
175    #[inline]
176    pub fn type_name(&self) -> &'static str {
177        match *self {
178            MaybeDebug::DynTrait { type_name, .. } |
179            MaybeDebug::TypeName { type_name } => type_name,
180            MaybeDebug::Slice(ref s) => s.type_name,
181            MaybeDebug::Str(_) => core::any::type_name::<str>(),
182        }
183    }
184    /// Construct a "passthrough" 'MaybeDebug',
185    /// that will simply delegate to the underlying implementation.
186    ///
187    /// On nightly, this is equivalent to [maybe_debug].
188    /// However, on stable, `maybe_debug` cannot specialize
189    /// and will unconditionally call [MaybeDebug::fallback].
190    ///
191    /// Therefore, this may be useful on stable rust
192    /// if you need to create a 'MaybeDebug' value from
193    /// a type that is unconditionally known to implement `Debug`.
194    #[inline]
195    pub fn passthrough<T: Debug + 'a>(val: &'a T) -> Self {
196        MaybeDebug::DynTrait {
197            val: val as &'a (dyn Debug + 'a),
198            type_name: core::any::type_name::<T>()
199        }
200    }
201    /// Construct a "passthrough" `MaybeDebug`,
202    /// that will simply delegate to `str`'s `Debug` implementation.
203    ///
204    /// Note that a regular `passthrough` cannot work because `str: !Sized`
205    #[inline]
206    pub fn passthrough_str(val: &'a str) -> Self {
207        MaybeDebug::Str(val)
208    }
209    /// Construct a "passthrough" `MaybeDebug`
210    /// that will simply Debug each individual value in the slice.
211    ///
212    /// On nightly, this is equivalent to [maybe_debug].
213    /// However, on stable, `maybe_debug` cannot specialize
214    /// and will unconditionally call [MaybeDebug::fallback].
215    ///
216    /// Even worse, it cannot even detect the fact the type is a slice.
217    ///
218    /// NOTE (for stable only): Until [RFC #2580](https://github.com/rust-lang/rfcs/blob/master/text/2580-ptr-meta.md)
219    /// is stabilized, this function will unconditionally
220    /// delegate to [MaybeDebug::fallback_slice] on the *stable compiler*.
221    /// This is still better than `maybe_debug` (or a plain `fallback`),
222    /// since it also prints the length. Users using nightly can ignore this warning.
223    ///
224    /// See also [MaybeDebug::passthrough] and [MaybeDebug::fallback_slice].
225    #[inline]
226    pub fn passthrough_slice<T: Debug + 'a>(val: &'a [T]) -> Self {
227        if NIGHTLY {
228            MaybeDebug::Slice(MaybeDebugSlice::from_slice(val))
229        } else {
230            /*
231             * Currently no way to construct a vtable on stable
232             * TODO: Fix once RFC #2580 is stabilized
233             */
234            MaybeDebug::fallback_slice(val)
235        }
236    }
237    /// Unconditionally use the "fallback" implementation for the specified slice.
238    ///
239    /// The current implementation simply prints the name
240    /// and the slice length.
241    ///
242    /// On stable this actually gives *more* information than [maybe_debug].
243    /// This is because `maybe_debug` cannot specialize to slices,
244    /// so it simply prints the type name without any length information.
245    ///
246    /// On nightly, this is strictly worse than [maybe_debug],
247    /// because it cannot specialize to the case that `[T]: Debug`.
248    #[inline]
249    pub fn fallback_slice<T>(val: &'a [T]) -> Self {
250        MaybeDebug::Slice(unsafe { MaybeDebugSlice::with_vtable(val, None) })
251    }
252    /// A fallback implementation that unconditionally prints the type name.
253    ///
254    /// For example `fallback<Option<usize>>()`
255    /// would print `"std::option::Option<usize> { ... }"`
256    ///
257    /// On stable, [maybe_debug] unconditionally delegates to this function.
258    /// On nightly, this is used if `T` is `!Debug` (and isn't a slice).
259    ///
260    /// However on nightly, if `T` is `Debug` it is strictly worse than [maybe_debug]
261    /// because it cannot take advantage of any `Debug` implementation for `T`.
262    ///
263    /// There is really almost no scenario in which you want to use this function.
264    /// [core::any::type_name] expresses the intent of printing the type name more clearly
265    /// and `maybe_debug` is able to take advantage of specialization  on the nightly compiler.
266    ///
267    /// It is included only for completeness.
268    /// NOTE: It doesn't actually require a value for `T`,
269    /// since it only uses its type name.
270    ///
271    /// See also [Self::fallback_slice] which also prints length information (which may actually be useful)
272    #[inline]
273    pub fn fallback<T: ?Sized>() -> Self {
274        MaybeDebug::TypeName {
275            type_name: core::any::type_name::<T>()
276        }
277    }
278}
279impl<'a> Debug for MaybeDebug<'a> {
280    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
281        match *self {
282            MaybeDebug::DynTrait { val, .. } => val.fmt(f),
283            MaybeDebug::TypeName { type_name } => {
284                f.debug_struct(type_name)
285                    .finish_non_exhaustive()
286            },
287            MaybeDebug::Slice(ref s) => Debug::fmt(s, f),
288            MaybeDebug::Str(s) => Debug::fmt(s, f),
289        }
290    }
291}
292
293/// A slice of elements which may or may not implement `Debug`
294///
295/// `MaybeDebugSlice` is to `&[T]` as `MaybeDebug` is to `T`
296/// 
297/// This is considered an implementation detail.
298#[doc(hidden)]
299#[derive(Copy, Clone)]
300pub struct MaybeDebugSlice<'a> {
301    elements: *const c_void,
302    len: usize,
303    type_size: usize,
304    type_name: &'static str,
305    debug_vtable: Option<backend::DebugVtable>,
306    marker: PhantomData<&'a ()>
307}
308impl<'a> MaybeDebugSlice<'a> {
309    /// Check if this type actually has any debug information,
310    /// returning `false` if this will just print the length.
311    #[inline]
312    fn has_debug_info(&self) -> bool {
313        self.debug_vtable.is_some()
314    }
315    /// Wrap the specified slice to implement `Debug`
316    #[inline]
317    pub(crate) fn from_slice<T: 'a>(elements: &'a [T]) -> Self {
318        let debug_vtable = if elements.is_empty() {
319            None
320        } else {
321            crate::cast_debug::<T>(&elements[0]).map(backend::retrieve_vtable)
322        };
323        unsafe { Self::with_vtable(elements, debug_vtable) }
324    }
325    #[inline]
326    unsafe fn with_vtable<T: 'a>(elements: &'a [T], debug_vtable: Option<backend::DebugVtable>) -> Self {
327        MaybeDebugSlice {
328            elements: elements.as_ptr() as *const _,
329            type_size: core::mem::size_of::<T>(),
330            len: elements.len(),
331            type_name: core::any::type_name::<T>(),
332            debug_vtable, marker: PhantomData
333        }
334    }
335    /// Get the length of the original slice
336    ///
337    /// This is available regardless of whether
338    /// or not the type actually implements debug.
339    #[inline]
340    fn original_len(&self) -> usize {
341        self.len
342    }
343}
344impl Debug for MaybeDebugSlice<'_> {
345    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
346        if self.len == 0 {
347            f.debug_list().finish()
348        } else if let Some(vtable) = self.debug_vtable {
349            let mut elements = self.elements;
350            let mut debugger = f.debug_list();
351            unsafe {
352                for _ in 0..self.len {
353                    debugger.entry(backend::from_vtable(
354                        elements, vtable
355                    ));
356                    elements = (elements as *const u8).add(self.type_size).cast();
357                }
358            }
359            debugger.finish()
360        } else {
361            write!(f, "[{} of {}]", self.len, self.type_name)
362        }
363    }
364}