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
TypeIdof a non-'statictype, can't find an outgoing of this. - Would non-`'static` TypeId be at all possible? asked if it would be even possible to make
TypeIdnon-'staticand 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