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.)
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>());
x.downcast_ref::<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 fn
s. 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.