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}