Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-impl `Debug` for all types when needed #2207

Open
prasannavl opened this Issue Nov 4, 2017 · 5 comments

Comments

Projects
None yet
5 participants
@prasannavl
Copy link

prasannavl commented Nov 4, 2017

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).
  • compiler relaxes the need for Debug as a trait, (in other words, it makes it invisible - and will simply derive it automatically if finds a call that implies (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.

@prasannavl prasannavl changed the title Analysis based auto-impl of `Debug` for all types Auto-impl `Debug` for all types when needed Nov 4, 2017

@le-jzr

This comment has been minimized.

Copy link

le-jzr commented Nov 4, 2017

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).

@scottmcm

This comment has been minimized.

Copy link
Member

scottmcm commented Nov 7, 2017

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.

@prasannavl

This comment has been minimized.

Copy link
Author

prasannavl commented Nov 14, 2017

@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.

@Centril

This comment has been minimized.

Copy link
Contributor

Centril commented Nov 27, 2017

@prasannavl I think @scottmcm is referring to the dbg!(..) (#2173) RFC. For details, see: [1], [2], and [3].

@Centril Centril added the T-lang label Dec 6, 2017

@matthiaskrgr

This comment has been minimized.

Copy link

matthiaskrgr commented Mar 6, 2018

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?

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 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.