#[derive(Debug)] by default

(Result::expect also requires E: Debug)

In that case a project using either .unwrap() or .expect() will have to take the hit to compilation times anyway.

I am of the belief that in principle it should be possible to derive a sane default debug implementation for pretty much every type, once questions like 'how to do that for raw ptrs' are answered. Whether that happens or not is more of a social issue at heart, not a technical one i.e. it's more about "do we want it? Under what circumstances do we want it? When don't we want it?" etc than it is about "can we even do it?".

1 Like

how about a new intrinsic:

  • when exact type is statically known
    • invokes derived or hand-coded Debug impl if present
    • returns symbolic type name otherwise
      • type name is looked up by TypeId in some kind of a global symbol table possibly assembled during linkage
      • binaries compiled without debug info will have no such table and as a fallback numeric value of TypeId will be used
  • method table for every trait object will include enough information to do an equivalent of the above dynamically
1 Like

Isn't this possible with specialization?

How do you see that working, in principle?

On the provided Result::unwrap example:

pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

mod spec {
    use super::Result;
    use core::any::type_name;
    use core::fmt::Debug;

    pub(super) trait UnwrapSpec<E> {
        fn unwrap_failed(msg: &str, error: &E) -> !;
    }

    impl<T, E> UnwrapSpec<E> for Result<T, E> {
        #[cold]
        default fn unwrap_failed(msg: &str, _: &E) -> ! {
            panic!("{}: `{}`", msg, type_name::<E>())
        }
    }

    impl<T, E: Debug> UnwrapSpec<E> for Result<T, E> {
        #[cold]
        fn unwrap_failed(msg: &str, error: &E) -> ! {
            panic!("{}: {:?}", msg, error)
        }
    }
}

impl<T, E> Result<T, E> {
    pub fn unwrap(self) -> T {
        use self::spec::UnwrapSpec;
        match self {
            Self::Ok(t) => t,
            Self::Err(ref e) => {
                Self::unwrap_failed("called `Result::unwrap()` on an `Err` value", e)
            }
        }
    }
}

fn main() {
    // thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: 10'
    Result::<(), _>::Err(10).unwrap();

    struct NoDbg;
    // thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: `playground::main::NoDbg`'
    Result::<(), _>::Err(NoDbg).unwrap();
}

Playground

EDIT: Alternative

1 Like

Inspired by @TimDiekmann (and others), why not introduce the following wrapper type:

#![feature(specialization)]
use core::fmt::{self, Debug, Formatter};

pub struct DebugWrapper<'a, T>(pub &'a T);

impl<'a, T> Debug for DebugWrapper<'a, T> {
    default fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "<{}>", core::any::type_name::<T>())
    }
}

impl<'a, T: Debug> Debug for DebugWrapper<'a, T> {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

Then, it could be used in generic functions (like Result::expect) to wrap arbitrary types, and could even by used in the code generated by #[derive(Debug)].

Playground link

1 Like

The use-specialization-to-attempt-Debug was proposed as part of dbg!, but was rejected there.

That might change once specialization becomes stable, but last it came up there wasn't appetite to expose it.