Thoughts about `type_name` for debugging

I found myself implementing this helper trait

trait TypeNameOf {
    fn type_name(&self) -> &'static str {
        core::any::type_name::<Self>()
    }
}

impl<T> TypeNameOf for T {}

and then

dbg!(some_value.type_name());

for debugging because

dbg!(core::any::type_name_of_val(&some_value));

is a little bit ugly.

I can think of 3 approaches to achieve "make it convenient to know types for debugging purposes":

  1. Impl the above TypeNameOf in core::any (replace the type_name call with intrinsics one of course).
  2. Impl fn type_name(&self) -> &'static str in core::any::Any. I won't recommend this, though it seems reasonable on the surface: what about types that are not 'static?
  3. Impl a declarative macro named dbg_typeof! in std with similar idea (and modified code) with dbg!:
macro_rules! dbg_typeof {
    ($val:expr $(,)?) => {
        // Use of `match` here is intentional because it affects the lifetimes
        // of temporaries - https://stackoverflow.com/a/48732525/1063961
        match &$val {
            tmp => {
                $crate::eprintln!("[{}:{}:{}] typeof({}): {}",
                    $crate::file!(), $crate::line!(), $crate::column!(), $crate::stringify!($val), core::any::type_name_of_val(tmp));
            }
        }
    };
    ($($val:expr),+ $(,)?) => {
        ($($crate::dbg!($val)),+,)
    };
}

(The form of the output message can be adjusted)

This is what I preferred because it emphasizes that type_name is "debug only" and should not be relied upon.

what are you doing that you need to learn the name of a type at runtime?

if i need to know the type of an expression, i usually just do let _: () = expr and read the compile error.

For example when testing performance and memory fingerprint difference in a particular scenario between HashMap and BTreeMap and maybe another data structure, you want to distinguish which data structure are the 2 or more printed test results belong to.

1 Like

I'm surprised that that comes up enough to be worth any extra setup. I'd think that that would be handled practically enough by giving explicit labels to the cases, which also lets you name non-type-related parts of the test like changes in the size or composition of the test data set.

fn benchmarks() {
    my_map_bench::<BTreeMap<String, f32>>("btree");
    my_map_bench::<HashMap<String, f32>>("hash");
}

Do you write a lot of benchmarks or tests that are solely distinguished by type?

2 Likes

The second expression isn't all that bad, is it? You could even use core::any::type_name_of_val and then it's super clean:

use core::any::type_name_of_val as type_name;
...
fn foo() {
    ...
    dbg!(type_name(&some_value));
}

Seems like a free function is just as good as an associated function? And better because it doesn't require adding anything extra?

1 Like