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

Intrinsic for type_name_of_id to power a better impl Debug for TypeId? #61533

Open
anp opened this issue Jun 5, 2019 · 11 comments
Open

Intrinsic for type_name_of_id to power a better impl Debug for TypeId? #61533

anp opened this issue Jun 5, 2019 · 11 comments
Labels
needs-fcp This change is insta-stable, so needs a completed FCP to proceed. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@anp
Copy link
Member

anp commented Jun 5, 2019

Currently TypeIds have uninformative derived Debug impls:

fn main() {
    println!("{:?}", std::any::TypeId::of::<usize>());
}
   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.91s
     Running `target/debug/playground`
TypeId { t: 8766594652559642870 }

This results in fairly poor Debug output for dynamic types like anymap.

I think it could be quite nice for debugging/logging/etc to allow printing the type name from a TypeId in the Debug impl. It would provide an out of the box improvement to debugging existing dynamic typing tools, and IIUC the contents of Debug impls in the standard library are not considered stable so there's neither a breaking change here nor a de facto stabilization of the type_name representation.

I assume this would need to rely on some unstable intrinsic being exposed to get the type_name of an ID at run time, but I'm not really aware what would be needed.

Thoughts? cc @oli-obk as we had discussed this a bit on IRC.

@czipperz
Copy link
Contributor

czipperz commented Jun 5, 2019

I think this is a cool idea. However, to implement this we would probably have to add some sort of type table to each application binary. That seems like it could become extremely expensive as each instantiation of a generic type would be stored separately.

@oli-obk
Copy link
Contributor

oli-obk commented Jun 5, 2019

The amount of extra memory needed is fairly limited, as most of these type names will already have ended up somewhere in the binary. So we could just have the other places refer to the table entries instad of having their own version of the name.

For release builds we could even just skip this table entirely and fall back to id printing.

Alternatively we can make the TypeId struct have a &'static str field in debug mode and just fill it in 😆

@Centril Centril added T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. needs-fcp This change is insta-stable, so needs a completed FCP to proceed. labels Jun 5, 2019
@Centril
Copy link
Contributor

Centril commented Jun 5, 2019

Alternatively we can make the TypeId struct have a &'static str field in debug mode and just fill it in 😆

Ostensibly we need something like this to deal with the soundness hole in #10389?

@oli-obk
Copy link
Contributor

oli-obk commented Jun 5, 2019

oh, you mean the solution where each TypeId is in fact backed by a (non-deduplicateable and non-duplicatable) static item and the TypeId is just a pointer to that? And the value of that static could be the type's name (or a zero length string in the release-mode version).

@anp
Copy link
Member Author

anp commented Jun 5, 2019

Interesting ideas! If I had my druthers it would be feasible to use this in release mode as well, as I am interested in using the output for logging.

@Centril
Copy link
Contributor

Centril commented Jun 6, 2019

@oli-obk Something in that spirit yeah.

@gnzlbg
Copy link
Contributor

gnzlbg commented Jun 13, 2019

I imagine this as follows.

We would have a:

trait TypeName {
    fn name(&self) -> &'static str;
}

that is implemented for all types:

impl<T: ?Sized> TypeName for T {
    #[cfg(rtti)]
    #[lang_item = "type_name_blanket_impl"]
    fn name(&self) -> &'static str;
    #[cfg(not(rtti))]
    fn name(&self) -> &'static str { "rtti-disabled" }
}

Then we would have a global compiler switch, e.g., -C rtti=on/off, that controls what the trait impls do. When -C rtti=on, TypeName::type_name implementations are magically generated to return the &'static str containing the name of the type are added for each type in the whole program. When -C rtti=on, only a blanket impl, e.g., returning "rtti-disabled", is provided.

@eddyb
Copy link
Member

eddyb commented Apr 9, 2022

(cross-linking) TypeId containing a mangled name would allow this more efficiently (in terms of binary size) than via std::any::type_name, and is arguably necessary for sound TypeId (at least one some platforms?): #77125 (comment)

@eddyb
Copy link
Member

eddyb commented Apr 9, 2022

Update: finally opened #95845 with a (v0) mangling-based TypeId representation (which could eventually be used to allow runtime demangling of type names from TypeId).

@infomorphic-matti
Copy link

infomorphic-matti commented Sep 5, 2023

Would it be possible to avoid the explosion of binary size if we simply avoided storing table entries for types that don't have TypeId::of::<T>() called on them? (or rather, the underlying intrinsic call)

The main problem I can see with this is the generic impl for core::any::Any, but most types in a given program are unlikely to actually be used as an Any.

I don't know if existing vtables for types that get turned into dyn Trait references are pruned to only include types that actually end up getting converted to dyn refs and objects, but I imagine a similar mechanism could be used if that is the case already :) - in this case, or the case of a more general "on-demand impl-instantiation" mechanism, any type that doesn't get used as Any doesn't get the trait implementation instantiated for it and you avoid the call to TypeId::of that would create the type name entry in the final binary.

That is perhaps a way to do it? Might also improve optimisation speed if this isn't already the case, since you avoid many instantiations of trait code for generic impls.

@eddyb
Copy link
Member

eddyb commented Sep 5, 2023

Might also improve optimisation speed if this isn't already the case, since you avoid many instantiations of trait code for generic impls.

The Rust compiler uses monomorphization, i.e. only the instances of generic functions (which includes fns in generic impls) which are actually required, get compiled (and even then, a backend, like LLVM, is free to optimize them out if actually unused - and this includes vtables, too).

A good way to think about it is that the possible set of types is effectively infinite - even if in specific cases there may be, at most, e.g. (on 64-targets) only 264 [(); N] types, the ability to combine types means that you can always make more of them (e.g. by combining existing ones into tuples), and the main limit on "how many types can exist at once" is caused by the resources needed by rustc (i.e. host RAM). So there is never a meaningful "list of types", only generic type definitions, and everything involving their instances (and instances of generic fns) is necessarily always on-demand.

(This is, incidentally, a significant historical factor in rustc incremental - the Rust type/trait system already forced a lot of compiler machinery to be on-demand, which was then repurposed for incremental tracking and skipping computations that couldn't have changed their results)


Shame #95845 didn't happen, it would've been a good way to get this capability into Rust.

Arguably that approach could be made opt-in, or handled via debuginfo instead (e.g. labeling a static that the TypeId points to, even if all that static contains is a bigger hash, not a full mangled type string), but I don't have any plans to return to the topic (and no really good ideas either).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-fcp This change is insta-stable, so needs a completed FCP to proceed. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

7 participants