Code using """pointers""" is inherently difficult/impossible to debug, because Debug on pointer types (Arc, references, etc) doesn't include criticial information

Pet peeves: dbg!(an_arc) doesn't show that it's an Arc, nor which instance of an Arc it is, dbg!(an_str) doesn't show that it's a &str, nor which instance of &str it is, etc. There are a few more cases like this in the stdlib. Is there any particular reason why the stdlib is unhelpful about debugging these types? These Debug impls that relate to references/smart pointers should include the pointer value while at it, so you can tell if you got two "identical" values mixed up somewhere. This is important both where interior mutability is concerned, as interior mutability is opaque to debugging, as well as where unsafe code is concerned, because pointer operations between different allocations are UB (particularly relevant with slices).

The general assumption is that you know the type when you're looking at the Debug formatting of some value. This is, ultimately, just an arbitrary decision made to reduce noise in the {:?} debug repr.

If you want the typename of the root object, you can easily just make your own dbg! (even shadowing the implicit prelude one) that also prints std::any::type_name. For non-root objects, you should have enough type context to know if something is boxed.

8 Likes

Note that rust-analyzer exists. It can show you the type of variables inside your editor.

5 Likes

You can implement that macro with std::any::type_name.

For example:

macro_rules! type_dbg {
    ($val:expr) => {
        match $val {
            tmp => {
                // Copy of `std::any::type_name_of_val`, which is nightly-only.
                fn type_name_of_val<T: ?Sized>(_val: &T) -> &'static str {
                    std::any::type_name::<T>()
                }
                
                eprintln!(
                    "[{}:{}] {} : {} = {:#?}",
                    file!(),
                    line!(),
                    stringify!($val),
                    type_name_of_val(&tmp),
                    &tmp
                );

                tmp
            }
        }
    };
}

Link to the playground.

2 Likes

Sorry, we may have misrepresented our point.

If the pointer value matters to debuggging, you're expected to use std::fmt::Pointer, {:p}.

For example:

macro_rules! dbg_ptr {
    ($val:expr) => {
        match $val {
            ref tmp => {
                // Copy of `std::any::type_name_of_val`, which is nightly-only.
                fn type_name_of_val<T: ?Sized>(_val: &T) -> &'static str {
                    std::any::type_name::<T>()
                }
                
                eprintln!(
                    "[{file}:{line}] {expr} : {ty} = [{ptr:p}] {val:#?}",
                    file=file!(),
                    line=line!(),
                    expr=stringify!($val),
                    ty=type_name_of_val(tmp),
                    ptr=*tmp,
                    val=tmp,
                );

                tmp
            }
        }
    }
}

In the common case, pointer identity doesn't matter for correctness (and where it does, the borrow checker handles it) so it's fine to leave it out and opt-in.

6 Likes