Would non-`'static` TypeId be at all possible?

Am I missing a reason we couldn't have TypeId just omit any information about lifetimes?

Because lifetimes don't exist at runtime

The reason is that TypeId's primary use is for checking type equivalence for <&dyn Any>::downcast. The current situation is that TypeId has no idea what lifetimes are, so it can only be used to identify 'static types (those that don't capture a lifetime other than 'static).

TypeId itself could be relaxed so that you could obtain the "'static type id" for a type even if the type you're starting with isn't 'static, but that's a huge potential footgun, and quite likely to not actually be useful in any way (beyond making it easier to write code that incorrectly treats TypeId as respecting lifetime compatibility, somehow).

Plus, it's not impossible to do use said footgun if you want to and think you can be careful enough to use it. (If you're anywhere near correct, you're most likely an application, not a library, at that.) Just make an adapter trait trait Static { type Static; } and implement it as necessary. (A default impl can't be provided without associated type specialization, unfortunately.)

1 Like

Hmm, that's true. What about something like fn downcast_ref<'a, T: 'a>(self: &(dyn Any + 'a)) -> &T work? (Also, how would both the current signature and my hypothetical signature deal with contravariance?)

P.S. Example of contravariance at play:

use std::any::{Any, TypeId, type_name};

pub fn downcast_ref<'ref_, 'val, T: 'val>(x: &'ref_ (dyn Any + 'val)) -> Option<&'ref_ T> {
    println!("{}, {:?}, {:?}", type_name::<T>(), x.type_id(), TypeId::of::<T>());

pub fn main() {
    println!("{:?}", downcast_ref::<fn(&i32)>(&require_static_lifetime).is_some());
    println!("{:?}", downcast_ref::<fn(&i32)>(&require_any_lifetime).is_some());

pub fn require_static_lifetime(_: &'static i32) {}
pub fn require_any_lifetime(_: &i32) {}


Apparently Rust somehow returns None in each case? Does it have a separate TypeId for each function?

In Rust, the fn type is not actually the type of fns. Each fn has its own zero-sized, unnameable type, which can then be coerced to the fn type.

The reason for this is so that if you write, say, [1, 2, 3].iter().map(my_fn), the generic function map will get its own instantiation for my_fn, which can then be inlined, rather than using one instantiation for all function pointers.


It would be useful in exactly the ad-hoc specialization case. You have a foo<T>() and want to identify "is T == f64"? Because f64 is lifetime-free, typeid equality between T and f64 means that T is f64 and you can safely go ahead.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.