Downcast `std::any::Any` with non-static lifetime?

Continuing the discussion from Calling the Any trait's type_id() on a mutable reference causes a weird compiler error:

The question is almost the same as linked, but I have further questions about its answer. That answer mentions the potential danger of casting a shorter lifetime to a longer lifetime. However, when we have a reference &'a dyn Any, we know this reference is good for 'a. In specific, everything it contains is at least 'a. When we downcast &'a dyn Any to a concrete type &'a T<'b>, we just need to ensure 'b (all of them if T has multiple lifetime specifiers) is no longer than 'a. Given that we can bound it using non-static lifetime, why does Any still need to be bounded by 'static?

Little simplification here, given that &'a T<'b> requires 'a to be no longer than 'b, if you also require 'b to be no longer than 'a then 'a must be the same as 'b, thus you're downcasting to &'a T<'a>.

That said, your reasoning assumes that a T<'long> is also a valid T<'short>, but this is true only for types that are covariant over the given lifetime. But there are also contravariant types, where the T<'short> is a valid T<'long> but not the opposite (think for example of a function type in relation to its input; if it requires a short lifetime then you can call it with a long one, but the opposite is not safe) and invariant types, where T<'long> and T<'short> have no relation (think for example of Cell and other types with internal mutability: you can't shorten the lifetime or you will be able to write a value with a shorter lifetime and someone else, who still knows the original long lifetime, will be able to read that value as if it had a longer lifetime, which is not safe).

4 Likes

I would think twice about how variance works here.

But we are not allowed to downcast only because we are not able to correctly figure out the associated lifetimes? Everything works well while using &dyn Any but we are not allowed to see what its type is because we are not able to? I mean, there are lifetimes that satisfy the constraints, right?

Yes, the problem is that in order to downcast you have to know a valid lifetime to use for the value that was originally used to create the &dyn Any, and this is not (always) possible.

I'm not sure what you're asking here. Given a &dyn Any, and assuming we can create one from a non-'static type, we won't able to see the original type, lifetimes included, at best we can see what was the original type with lifetimes erased.

Again, I'm not sure what you're asking because this depends on the previous question, but assuming we can create a &dyn Any from a non-'static type, then there is always a valid lifetime to downcast it to, the one of the non-'static` type we created it from!


By the way if you want to look at a way a non-'static Any could work, you can take a look at the better_any crate.

I’d say, a huge problem of making this fit into Any is that Any already supports types with lifetimes. The lifetimes just need to be 'static.

I think your rule, i.e. convert &'a T<'b> into &'a dyn Any - requiring 'b to be covariant (and implicitly requiring 'b: 'a – and to convert &'a dyn Any back into &'a T<'b1> requiring 'a: 'b1 is sound. As @SkiFire13 mentioned, it can be simplified to converting &'a dyn Any back into &'a T<'a> at the other end.

Anyways, this rule is in contradiction with what happens today: converting &'a T<'static> into &'a dyn Any without requirements of covariance, and then downcasting back into &'a T<'static>. There’s just no way to unify these rules: The requirements going in are orthogonal… one is 'b must be 'static, the other is 'b must be covariant. The type coming out is very different, too: containing lifetime 'a or lifetime 'static. I could imagine for the latter point, perhaps, to use different type ids for the different ways of treating a lifetime. But then, if you start out with some x: &'a T<'b> value, what way would there be to tell the compiler which of these versions you want? Should your “x as &dyn Any” introduce a requirement of “'b == 'static” or a requirement of “'b is covariant”?

You cannot simply make this choice automatically either. The way the borrow checker works, you cannot make compilation choices based on asking a “question” like “is this lifetime 'static”? The only kind of “question” the borrow checker is designed to answer is the question “does this program compile?” and the only way of relating a lifetime to 'static is to either require it to be 'static (which may or may not make the program no longer compile).

And while you can probably easily check for “'b is covariant”, the natural rule “if 'b is not covariant, then require 'b to be 'static and return a dyn Any of the respective kind” is useless, too, since that weaker precondition ('b covariant) only allows us to get a “weaker”/less-useful type out on downcasting, too: &'a T<'a> instead of &'a T<'static>.

On that note, for downcasting to &'a T<'b>, same problem: What constraint should be introduced? 'b == 'a or 'b == 'static?


If you need the user to be explicit on how which lifetime is treated, then it doesn’t fit with the existing dyn Any. E.g. the better_any approach that’s already mentioned solves the issue by requiring the user to choose the behavior between the two behaviors explained above, for each lifetime, through use of a trait. This means that the “treat which lifetime how” question can only be answered once for every type, but I suppose users can work around this with a newtype-pattern.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.