Why does Any only work for T: 'static?
I was looking through the source for Any, and I see this in the comments:
//! Traits for dynamic typing of any `'static` type (through runtime reflection)
//!
//! This module implements the `Any` trait, which enables dynamic typing
//! of any `'static` type through runtime reflection.
...but no justification for that. Digging a bit deeper, it seems it might be because it uses TypeId::of::<T>(), which is an intrinsic:
extern "rust-intrinsic" {
...
/// Gets an identifier which is globally unique to the specified type. This
/// function will return the same value for a type regardless of whichever
/// crate it is invoked in.
pub fn type_id<T: 'static>() -> TypeId;
}
...which is T: 'static. I don't see why though?
What's so special about 'static types that they get a TypeId that other types don't?
Seems weird.
Posted Jan 2, 2015, 2:09 AM UTC
A non-
'statictype is a type that contains non-'staticreferences, e.g.&'a Twith'a!='static. I believe the concern is ensuring that downcasting is safe is much harder if there are lifetimes, and it's likely infinitely less useful. I think the TypeId would have to include some representation of the lifetime that ensures that one can only downcast to references which are valid.(At the moment, I believe the TypeId is a hash that just completely discards lifetime information.)
The type ID hash takes higher-rank lifetimes into account, so for example
fn(&'static int) -> &'static intandfor<'a> fn(&'a int) -> &'a intwill be given different type IDs. Everything else has been discard by trans by the time the hash is computed.Do you hang out in #rust-internals at all? I ask because I don't generally expect random internet commentators to have any intimate knowledge of trans, or to have ever even heard of higher-rank lifetimes. :)
If not, you should come say hi!
I submitted an issue for this about a week ago link.
Though it is unsafe to downcast a type with non-static references it should still be possible to allow non-static types to retrieve a TypeId, no?
Would this mean that every instantiation of a type with different lifetimes had a different TypeID? This increasingly feels like it's generating a lot of rarely-useful work for the compiler (hashing a bunch of stuff on the offchance that someone might want to reflect on it).
I really like the idea of adding a
Reflectbound requirement in order to be able to generate a TypeID for a type. That might help limit the amount of unnecessary work done.#[deriving(Reflect)]seems like it would be a worthwhile thing.Generating a TypeId for each instantiation of lifetimes is maybe not useful. However it is currently not possible to get any sort of TypeId at all once you deal with a non-static type. I guess if I have a type Foo<'a> I would like to be able to retrieve the TypeId for Foo<'static> at least.
Ah, I see what you're saying. Yeah, that would probably be useful. That would probably just require an interface change, I guess.
So basically, 'it's kind of complicated to implement TypeId with lifetimes, so we didn't'?
I guess that's a fair call, but it's a bit disappointing, considering it's marked as stable, and means that we're stuck with no real reflection story for a broad class of struct's in the post 1.0 world.
I don't know if there's a deeper reason, I also don't know if it's at all possible to take into account lifetimes in a way that is actually useful.
It's quite possible to add a .downcast_lifetime<'a, T> to AnyBoxExt and friends, if TypeId is changed to include lifetime information, post-1.0.
Making things more permissive is generally backwards-compatible. We're taking the conservative approach for the first stable release in order to give us as much future wiggle room as possible.
Oh... so even stable APIs are subject to non-breaking changes?
I was under the impression that stable -> locked down post 1.0.
If it can't break their code, a user doesn't need to mind that an interface has become more permissive. The trick is making sure it can't break their code... and I'm not saying that this specific change wouldn't, I'm not an expert in this area.
I am not totally sure I understand how the
Anyinterface would actually work with a type with lifetimes, though. Lifetimes are (1) a compile time construct, and (2) tractable because they have to be attached to a type throughout its natural life. For trait objects, my understanding is that this is done this is by limiting its lifetime through a reference (&'a SomeTrait,&'a mut SomeTrait) or through aBox(and later other smart pointers likeRc) with an explicit lifetime (Box<SomeTrait + 'a>).But a single lifetime only always works with references (as a sort of lower bound), it can't be used to derive what are potentially multiple lifetimes in the type you're casting to (unless it's
'static, in which case you know that all of its lifetimes are'static). Otherwise you can't really know what they are at compile time, hence they can't be used for safety. But I haven't thought this through carefully, so I could be missing a way to get these working. Can you walk me through it so maybe I understand how you expect it to work?Any<'a> stores an instance of Foo<'a> I suppose. I dont know how youd implement that, but I dont see why it would particularly troublesome. For legacy behaviour use Any<'static>.
Im more concerned with the fact TypeId doesnt support all types.
That isn't a general solution. There are types with multiple lifetime parameters. You'd have to know the relationship between those lifetimes and other lifetimes in scope, wouldn't you? Like I said, there may be a way around this. Maybe you could do something with variadic generics?
I get what you're saying about TypeId, but keep in mind that lifetimes are a bit weird because they're pretty much pervasively parametric, i.e. creating a new TypeId for every possible Type-lifetime combination might be really expensive. The same can be true of generic types but at least those aren't always generic.
(Also, it occurs to me that since lifetimes are really bounds, equality comparisons on them wouldn't really make a whole lot of sense since they actually express subtype relations. You can [currently] talk about a lifetime being longer or shorter than another one, but asking if they're exactly equal feels less useful. If you take
SomeType<'a>in a function, andSomeType<'b>with some additional, shorter bound on'bin another function (so'a: 'b), do those two have differentTypeIds? Baking them into theTypeIdwould necessitate that, but that wouldn't capture that it's safe to upcast fromSomeType<'b>toSomeType<'a>.I think something more like 'outlives' as its own, separate concept would be more appropriate here, if you had anything at all. Then you could theoretically cast from
Any<('x,'y,'z)>toFoo<'a,'b','c>[where all three are lifetimes in scope] iffFoohas the sameTypeId,'a: 'x,'b: 'y, and'c: 'zor something.But on that note, how would you handle recursive functions with lifetimes? I assume we can handle them by not actually expanding them [only having to do local reasoning] but that falls apart if we have to assign unique
TypeIds, doesn't it? Would it be safe to assign aTypeIdthe same lifetime if it shows up in the same scope in the same function, regardless of recursion? Maybe it would. It's really not at all clear to me though--at best, it feels like that would make it generally impossible to compare lifetimes orTypeIds across functions, outside of'static. That feels like it would be severely limiting the usefulness of calling it for these types).