Debugging non-`Debug` items in debug releases

Imagine you're knee deep in some library code that is generics heavy when something unexpected happens.

You'd like to dbg!(...) a few things to take a peek at what's going on. Alas, while they very likely concretely implement Debug, you haven't littered Debug all over your generic trait bounds so Rust politely tells you it can't do it.

What to do?

The obvious answer is to go litter Debug over all of your trait bounds until things work and, as far as I'm aware, is all you really can do today short of stepping through with a debugger which isn't really the same thing and slightly misses the point.

What I want to reach for (and what doesn't exist) is some magic to just let me auto-Debug::fmt(...) this dang thing I want to peek at during development without adjusting a half dozen or more trait bounds across my code base.

In principle, there is no reason this couldn't be done in a number of ways (some more horrifying than others):

  • Auto-implement Debug for all items, manual Debug impls specialize (discussed previously) - doesn't really fix the trait bounds thing but maybe the compiler could then assume all types are Debug idk
  • A magic debug_dbg!(...) function/macro that, similar to debug_assertion!(...) is stripped outside of debug builds. It would (in-effect, if not in practice) impl Debug for its argument if it wasn't already implemented. There might need to be an intrinsic for that because macros can't do that without more trickery than I'm aware of.
  • I'm sure there are other options I'm not sufficiently clever to think of.

To re-iterate and head off some questions:

  • the desire is for something like dbg!(foo) to "just work"
  • the reason why #[derive(Debug)] doesn't necessarily fix this (and really invalidates some of the above options too), is that the problem can be trait bounds which shouldn't need to convey Debug bounds down several layers of generics to have sane debugging during development.

I realize that the current situation isn't too bad, but I think it could be better and I'm interested in hearing what people think or if this is a ergonomics paper cut only I feel.

2 Likes

Here's a slight alternative that might also work for you: provide a "maybe debug" trait that's effectively your "specialized debug." It wouldn't work for types that don't actually implement debug (printing either an empty string or just any::type_name), but it would print the debug format if it did exist. (An advantage is that types that are deliberately not Debug to avoid information leakage won't have a hole here.)

I don't quite understand the restrictions on current specialization. My attempt at providing a default implemented debug adapter doesn't work because error: cannot specialize on trait `Debug` but I believe that this is theoretically okay, and probably more palatable than providing actual fake Debug impls for types without Debug impls.

(That said, if you are eventually able to specialize on Debug, I forsee a lot of generic types switching to a specialized debug impl to always impl debug even when the contained type doesn't.)


To the original problem, though: a potential solution rather than putting : Debug bounds everywhere is to loosen your Debug impl to not require Debug, and ignore the generic type for the debug print. If you only care about inspecting container state, this is enough (even if not exactly trivial to do (I think the derivative crate allows you to derive an impl like this?)). If you need to inspect the state of the generic type, well, ¯\_(ツ)_/¯

3 Likes

It's because Debug is apparently not marked as AlwaysApplicable so it is rejected. EDIT: actually that line is looking for Marker but AlwaysApplicable probably works somewhere else...

It is unclear to me from the definition of 'AlwaysApplicable' why Debug would not meet that, but thats a bit outside my wheelhouse.


If you need to inspect the state of the generic type, well, ¯_(ツ)_/¯

Sadly, this is the scenario I'm describing.

Every now and then I come back to and use https://docs.rs/debugit/ (Note: requires nightly and not maintained) which uses specialization to print the Debug formatting of a type, if available. It's useful in this scenario. Something like what you are proposing can use that + more magic to make dbg!() almost always work, and it's a worthy end goal.

4 Likes