Currently, two traits control the print fmt - Display and Debug. Display makes sense to be manually implemented. However Debug trait not only just quickly becomes tedious, but soon ends up in trait bounds and then starts to really pollute the code, even when it's unrelated to the problem space.
TL;DR:
format! ("{:?}", x) implies #derive[Debug] for typeof(x).(x as Debug).fmt)Eg:
Let's say one writes a naive algorithm for quicksort. And as a good programming citizen, leaves his/her debug/trace code, that may or may not get removed in the release build.
fn sort<T:Ord + Copy + Debug>(arr: &mut [T]) {
let len = arr.len();
if len < 1 {
return;
}
let mut wall = 0;
let mut index = 0;
let pivot = arr[len - 1];
debug!("pivot: {:?}, len: {:?}", pivot, len);
// ..snippety snip.. //
debug!("swap {:?} [index:{:?}] and {:?} [index:{:?}]",
arr[wall], wall, arr[index], index);
// ..snip.. //
}
debug!("wall: {:?}", wall);
// ..snippety snip.. //
}
Well, the problem here is I need the Debug trait bound, even though, it isn't a part of the actual problem space. And now every type passed to this generic method has to implement it in order for it to work. This is problematic. I think Debug has the potential to pollute the ecosystem this way.
Solution:
Learning from other languages (Go, .NET etc) - these compilers just do it for you automatically. Go runtime does the job of printing out, and .NET, for example automatically derives .ToString(). However, this is not ideal for the a zero-abstraction language like Rust, especially since some of them also use Reflection.
So, what I'd like to explore, is the possibility of letting the compiler auto-implement Debug in addition to #[derive(Debug)] for whichever types that leaves hints that it requires it. Hints could be in the form of compiler support that can be activated by the format! macros, and or more sophisticated analysis. So, there's always a default implementation, that provides a sensible format like Go. While a language like Go uses reflection, in Rust the compiler should have sufficient knowledge to print all of the data anyway, with the exception of references and pointers (which can just simply print the pointer values, since this can be overridden and by implementing the Debug trait manually.)
Hypothetically, if we assume this is exists, then in the above example Debug will no longer be a trait bound, since we can safely let the compiler satisfy it when needed. And since the compiler already has knowledge of what T is going to be, it will simply mark the type T to do #derive[Debug] when the compiler has direct access to T, or do a side implementation for it in the library being compiled, as it sees the format! macros, or whichever the mechanism to drop the hints are. Just to clarify - the format! macro is just a naive example to express the idea - it may or may not be the right place to drops the hints. But since Debug trait only really makes sense inside a format, I think that would be one way to do it. Or may be simply finding out all calls that imply (x as Debug).fmt would make more sense.
This effectively allows Debug to mostly become a forgotten hero behind the scenes. And println!("{:?}", x) should just work automatically.
However, I don't have knowledge of the compiler internals at the moment, so I'm unsure of the implementation details. I hope someone can shine some light on it.
EDIT: A little note on why analysis might be required as some have mentioned why not just let the compiler auto impl whenever needed - This will work fine for rlib and binaries (in fact, could be even the preferred way to do it), however it won't for dynamic libraries.
I would say that it's unnecessary to do analysis. Just have the compiler auto-derive Debug when it's not defined manually. If you are worried about unnecessary work in the compiler, it might be possible to only generate debug implementation when the compiler needs it (even as far down the road as when a final program binary is compiling).
The dbg! discussion also talked about the "Adding :Debug is annoying" problem, but envisioned a different solution using specialization to call debug if possible and give something else if not.
@scottmcm Is this the one you're talking about: https://www.reddit.com/r/rust/comments/6poulm/tip_print_a_t_without_requiring_t_debug/
If so, that helps. But it requires the type to be specialized. I find that again to be leading down the same hole. Except now, it compiles successfully and prints messages that aren't really helpful when you don't have explicit specialization.
I think having a system similar to how Go does it, would be most useful.
If I make a struct derive Debug, I can get access to private struct members that way.
If everything auto-impl'ed Debug woud that pose some security concerns?
````rust
mod a {
#[derive(Debug)]
pub struct Foo {
pub x: i32,
y: i32,
}
impl Foo {
pub fn new(x: i32) -> Foo {
Foo { x, y: x * 2 }
}
}
}
fn main() {
use a;
let a = a::Foo::new(3);
//println!("{}", a.y); // does not work!
println!("{:?}", a); // does work, due to Debug
}
=>
Foo { x: 3, y: 6 }
````
I agree with @matthiaskrgr: Debug should not be automatically implemented in such a way as to display internal data. (For crypto-PRNGs in Rand we explicitly implement Debug in such a way that it doesn't display state.)
However, I feel like the proposal is a good idea: Debug should be automatically-implemented in some way (e.g. Foo { <hidden> }) for all types where not otherwise implemented.
This would make the <X: Debug> bound redundant (nice for a few things, like making .unwrap() work on all Results).
Ideally the compiler would warn when it detects a concrete type using the automatic implementation of Debug (but not for generic types).
@matthiaskrgr Well, if this should hypothetically get included and the compiler auto implement Debug - I'd think the logical way would be to introduce an inverse attr _to never auto impl_ and opt out, that'll be a part of that impl, which can be used by all the security sensitive libraries -- if manually implementing Debug isn't ergonomic for them. Would that not be take care of these?
I'd still guess, for this all to work, it needs to a emit warning, but still compile - so may be print a standard message for these no debug cases - I think this can even just be the path name.
Or even #[derive(?Debug(message = "This is sensitive stuff, can't print"))]
(Bikeshed names, obviously, but for the sake of the example using ?Debug - though it could also be something with it's own attr)
For instance, your example:
mod a {
#[derive(?Debug)]
pub struct Foo {
pub x: i32,
y: i32,
}
impl Foo {
pub fn new(x: i32) -> Foo {
Foo { x, y: x * 2 }
}
}
}
fn main() {
use a;
let a = a::Foo::new(3);
//println!("{}", a.y); // does not work!
println!("{:?}", a);
}
Output:
[a::Foo]
Most helpful comment
The
dbg!discussion also talked about the "Adding:Debugis annoying" problem, but envisioned a different solution using specialization to calldebugif possible and give something else if not.