To_debug, a Debug counterpart of to_string

The ToString trait provides a shorthand object.to_string(), equivalent to using Display to format the object as a string.

Several times lately, I've found myself wanting an equivalent shorthand that uses Debug to format the object as a string.

Does it seem reasonable to add a method like to_debug for that?

1 Like

It would need to be a separate trait from ToString, right?

Yes. It would look something like this:

/// A trait for converting a value to a `String` via [`Debug`].
///
/// This trait is automatically implemented for any type which implements the
/// [`Debug`] trait. As such, `ToDebug` shouldn't be implemented directly:
/// [`Debug`] should be implemented instead, and you get the `ToDebug`
/// implementation for free.
///
/// [`Debug`]: ../../std/fmt/trait.Debug.html
#[unstable(feature = "todebug", reason = "new API", issue="...")]
pub trait ToDebug {
    /// Converts the given value to a `String` via `Debug`.
    ///
    /// # Examples
    ///
    /// Basic usage:
    ///
    /// ```
    /// let s = "hello";
    /// let hello = String::from("\"hello\"");
    ///
    /// assert_eq!(hello, s.to_debug());
    /// ```
    #[unstable(feature = "todebug", reason = "new API", issue="...")]
    fn to_debug(&self) -> String;
}

/// # Panics
///
/// In this implementation, the `to_debug` method panics
/// if the `Debug` implementation returns an error.
/// This indicates an incorrect `Debug` implementation
/// since `fmt::Write for String` never returns an error itself.
#[unstable(feature = "todebug", reason = "new API", issue="...")]
impl<T: fmt::Debug + ?Sized> ToDebug for T {
    #[inline]
    default fn to_debug(&self) -> String {
        use fmt::Write;
        let mut buf = String::new();
        buf.write_fmt(format_args!("{:?}", self))
           .expect("a Debug implementation returned an error unexpectedly");
        buf.shrink_to_fit();
        buf
    }
}

Can't we just add a method on Debug to do this conversion? I'm pretty sure ToString was a mistake, and I don't see the reason to add yet another trait for this. Ideally, to_string would be on Display, but it's too late for that (because there is likely some code that relies on ToString, directly). Also, making a new trait would create some confusion, should I implement ToDebug directly, or implement Debug. The correct answer is always Debug because it is far more applicable.

2 Likes

No objection here. I don't have the history of ToString. If we can add a method to Debug instead with a default implementation, that seems perfect.

Note that neither Debug nor Display are in the prelude. I believe that this is because their fmt methods conflict. If .to_debug() is on Debug, it will require importing std::fmt::Debug to use the method.

Interestingly, if fn to_string(&self) -> String had been a method on Display rather than its own trait, we wouldn't have needed specialization to specialize str/String/Cow<str> ::to_string.

I assume that to_debug isn't going to be used that much, so this shouldn't be a problem

Debug is in core and String is in alloc so none of the methods can mention String. So it will have to be done the same way as ToString.

5 Likes

Oh, interesting.

I imagine that could work differently in a future modular std, but in the meantime, sounds like we do need a ToDebug to make this work.

Off topic for this thread, but I just want to jump to the defense of ToString. It's really great as a teaching tool when explaining traits:

  • It has exactly one method with a straightforward and obvious signature, making the behavior very easy to explain.
  • It implements an interface which is very common in other languages, so users are familiar with the idea.
  • It's in the prelude, so you don't need to worry about imports being wrong.
  • It's object safe, so you can use it to teach about trait objects.
  • It has a blanket impl, so you can use it to teach about blanket impls.

I love ToString for these reasons alone, and its also my go-to whenever I need to create a trait object in a playpen to test something.

5 Likes

x.to_debug() is just minimally more convenient than format!("{:?}", x). Debug also supports formatting, e.g. pretty-printing mode, so you'd need a method or an arg for format!("{:#?}", x).

I don't think it's worth making the change, especially that Debug output as a string is mainly used for ugly hacks like parsing the debug output to get private details of a type.

3 Likes

"mainly"? I'm sure that people do that, but that seems unlikely to be the primary use case for rendering Debug output.

One of my use cases for this: error types whose Debug output provides a full backtrace.

1 Like

"mainly" is subjective of course. It's my impression from questions on forums and mentions elsewhere on the net that I've seen.

Note that you described a case for generating a nice Debug format, and printing it. That doesn't necessarily require capturing of the Debug output as a String, and especially not to a degree that would require a convenience function for it.

1 Like