[Pre-RFC] TypeId for non-static Types


#1

Summary

Remove the 'static bound from the type_id intrinsic so users can experiment with reflection over non-static types.

Motivation

A common method for storing a map of arbitrary data is to use something like a HashMap<TypeId, Box<Any>>. The problem is that a TypeId can only be constructed for a static type. This is a reasonable constraint for the stable API, because lifetimes may need to play a part in equality checks by type id. However there are cases where a user is boxing trait objects to work around lifetimes, so they only need to guarantee that data stored with a particular key is of a particular type.

This can be worked around on Rust now by using a trait with an associated type that’s expected to be a 'static version of the implementor:

unsafe trait Keyed {
	type Key: 'static;
}

struct NonStaticStruct<'a> {
	a: &'a str
}
unsafe impl <'a> Keyed for NonStaticStruct<'a> {
	type Key = NonStaticStruct<'static>;
}

The Keyed trait needs to be marked as unsafe because it could lead to undefined behaviour if implemented incorrectly and used for transmuting memory.

This RFC proposes simply removing the 'static bound from the type_id intrinsic, leaving the stable TypeId and Any traits unchanged. That way users who opt-in to unstable intrinsics can build the type equality guarantees they need without waiting for stable API support.

This is an important first step in expanding the tools available to users at runtime to reason about their data. With the ability to fetch a unique type_id for non-static types, users can build out their own TypeId or Any traits.

Detailed design

Remove the 'static bound from the type_id intrinsic in libcore.

How We Teach This

This changes an unstable compiler intrinsic so we don’t need to teach it.

Drawbacks

Alternatives

Create a new intrinsic that’s specifically designed never to take lifetime information into account. This intrinsic would behave exactly as type_id does now. Having a totally separate intrinsic means type_id could evolve without breaking existing expectations.

Unresolved questions


#2

I think at the least that the type_id intrinsic should allow T without lifetime bound, so that we can prototype something out of tree.


#3

Do changes to intrinsics require an RFC?


#4

I’ve decided to just turn this topic into a Pre-RFC for removing the 'static bound from type_id. I’m not sure how much detail this particular request needs.


#5

Hmm. I’m not 100% opposed to this. It is certainly subtle that values with two distinct types can nonetheless have an equal TypeId. I wonder if there is an alternative name we could give that would make this clearer. OTOH, it’s subtle, but we can certainly document it.


#6

Correct me if I’m wrong, but right now the TypeId assumes a type with ‘no’ lifetimes is equivalent to a type with only 'static lifetimes, because lifetimes aren’t available to the intrinsic right now. This assumption isn’t exposed directly because of the 'static bound, but is still there because maintaining backwards compatibility would mean keeping TypeIds that exist right now the same.

I can’t think of a concrete usecase for reflecting over lifetimes at runtime though, so I’m not sure if assigning the same TypeId to Struct<'static> and Struct<'_> is the right call or not.

We could solve this on the intrinsic with documentation and when it comes time to add lifetime support to TypeId the problem can be solved there based on the motivations of the RFC.


#7

I just came across this again. Has there been a discussion about introducing a RuntimeTypeId as an alias that comes without the static restriction?


#8

There’s an accepted RFC for removing the restriction on the type_id intrinsic that could be used to build a RuntimeTypeId, but I think it’s run into issues in implementation.