Earlier discussion about this topic
- [Pre-RFC] TypeId for non-static Types - #7 by mitsuhiko resulted in an RFC which got merged but then retracted.
- Pre-RFC: non-footgun non-static TypeId - #15 by bjorn3 did something very niche and wouldn't allow getting a
TypeId
of a non-'static
type, can't find an outgoing of this. - Would non-`'static` TypeId be at all possible? asked if it would be even possible to make
TypeId
non-'static
and came to the conclusion that we would somehow need to maintain lifetimes, although they get deleted in codegen and don't exist in runtime.
Motivation
What I want is pretty simple. I have a library and want a TypeMap (HashMap<TypeId, Box<dyn Any>>
) as an intermediate storage for the user to insert to and retrieve from, but with values that don't live for the whole program.
Thought Process
In basic rust thinking this shouldn't be too hard. Just make the dyn Any
have an additional lifetime and make sure that we can only insert values that live for at least that lifetime. Because TypeId
doesn't say anything about the lifetimes, and definitely can't give us them as usable lifetimes, when we downcast the value back to the type, we should get back a type where all the lifetimes are the one of the typemap. Remember, shortening lifetimes is safe and is done all the time.
So that was the plan, and it has a lot of problems. Both TypeId
and the Any
trait, basically the whole type-erasure suite only works for 'static
types.
With the knowledge of the discussion from Would non-`'static` TypeId be at all possible? I knew that TypeId
doesn't have any rules for lifetimes and with the reaction of closing the RFC linked at the top, I knew that people don't want to do changes to it either.
So, what did I do? Simple. I made my own TypeId
. But with extras. It is a wrapper around TypeId
with the rule that it excludes the lifetimes. Basically, it should just get the TypeId
of the 'static
version of the type. Then, I just write my own LifetimedAny<'life>
which doesn't have a 'static
requirement, but just for 'life
, make it use my id for comparison, and cast the type to a type where all the lifetimes are 'life
on downcast.
Now, how will I do that? Let's just grab our beloved unsafe
block and.. oh.. In rust, the lifetime language, we can't play with lifetimes in unsafe
blocks. cries. grabs proc-macro. So I made a trait which has an associated type with a lifetime generic which should give me the same type as the one who implements it, just with all the lifetimes being replaced with the given one and made it implementable via a derive macro. Now that we have the 'life
version of a type and have checked that the type represents (my way of the Any::is
method, excluding lifetimes), we need to actually downcast our value into that type. Which we... can't.. We just can't assure that the associated type is the same size as the implementor, let alone the same type. So we also need to give the trait the functions to cast it.
And voila, my experiment: GitHub - DasLixou/zonbi: Type-Erase tools for non-`'static` types Zonbi. (japanese for zombie, because zombie-types. found it funny. zombie
was already taken)
Come to a point, what do you want?
Good question. Really, what is this? An Pre-RFC? A help-cry?
I'm not sure either. I am pretty happy about the prototype I came up with, and it works like a charm!
The problem is, that it is a proc macro, thus it has the problems every other proc macro has. The end-user of my lib also needs to import the zonbi
crate because $crate
paths don't exist for proc macros and they also need to put #[derive(Zonbi)]
on every type they wan't to put into it. Just imagine you would have to put #[derive(Any)]
on every type. You wouldn't want that. But other than Any
, Zonbi
isn't just a trait we can commonly implement, we need to do it for every type, even if it doesn't have any lifetimes, because of lifetimes. This is pretty stupid, but currently can't be described in any wise in the standard library.
Coming back to what this is, it's probably all of those. A point to gather information and ideas, I want to see where else people want this. A place to discuss how we could implement this automatically but still have it written down in the standard library (hopefully without a #[heh_just_magic]
compiler internal attribute). A topic to discuss this since 2016 wished problem and hopefully find a solution.
Feedback welcome, thanks <3