Calling Any::downcast_ref requires 'static?

I've been playing around with this snippet, and I'm stuck on this error:

error[E0759]: `self` has lifetime `'a` but it needs to satisfy a `'static` lifetime requirement
  --> src/lib.rs:36:27
   |
33 |     fn get_single_chunk<'a, T>(&'a self) -> Option<&'a T> {
   |                                -------- this data with lifetime `'a`...
...
36 |         for chunk in self.chunks() {
   |                      ---- ^^^^^^
   |                      |
   |                      ...is captured here...
37 |             if let Some(c) = (&chunk as &(dyn Any + 'static)).downcast_ref::<T>() {
   |                               ------ ...and is required to live as long as `'static` here

I'm stuck on this. Yes, the reference it's is 'a, but looking at Any::downcast_ref, that reference seems okay. The trait requires that any captured lifetimes are 'static (that is, trait Any: 'static {, and that should be here, too, since it's a &dyn Chunk and Chunk: Any. So, I'm at a loss here, as to what ails the compiler. My best interpretation of this is that it wants &'static dyn Chunk… which isn't what I want.

I've also tried it w/o the explicit cast:

error[E0599]: no method named `downcast_ref` found for reference `&dyn Chunk` in the current scope
  --> src/cr3/mod.rs:81:36
   |
81 |             if let Some(c) = chunk.downcast_ref::<T>() {
   |                                    ^^^^^^^^^^^^ method not found in `&dyn Chunk`

Which also doesn't make sense to me, since Chunk: Any, again.

Where am I going wrong?

downcast is defined on dyn Any, not on the trait Any, but that's a rather subtle distinction. There isn't a way to cast one trait object to another automatically just yet (with syntax like &chunk as &dyn Any), but we can do it manually. My preferred way is to use something like this:

impl<T: Any> AsAny for T {
    fn as_any(&self) -> &dyn Any { self }
}
trait AsAny {
    fn as_any(&self) -> &dyn Any;
}
trait Chunk: AsAny { ... }

then you can do this to cast to a concrete type!

chunk.as_any().downcast_ref::<SomeType>()

Or you can reimplement Any::downcast_ref on dyn Chunk, but that's subtle and requires unsafe.

Here's the AsAny solution adapted to you snippet: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d49bdb0e04ef09e7b1d14a32db797eb2

Here's the version implementing downcast_ref directly on dyn Chunk + 'static: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=97d4b91e3053694de354a801ec8cddf7

edit: The AsAny trait is separate so that you can give that blanket impl and not force your users to write the trivial impl of AsAny::as_any every time they impl. You can't put Chunk::as_any directly, because that would require your users to give the trivial impl, or Self: Sized bound if you insist on a default impl, which defeats the purpose of as_any. So the separate trait AsAny is required for both ergonomics and correctness.

&chunk has type &&'a dyn Chunk, so in order to convert that to dyn Any, the reference's target (&'a dyn Chunk) must implement Any. But &'a dyn Chunk can't implement Any because it's not 'static. Even if it implemented Any, you wouldn't be able to return the resulting value because &chunk is a temporary value limited to the body of the for loop.

chunk as &dyn Any would be closer to what you meant to do, but this is not a valid cast. Currently you can't convert a trait object to a trait object of a different trait.

chunk.downcast_ref() would work if downcast_ref was a member of Any trait. But this is just a regular function implemented on dyn Any + 'static, so your trait won't inherit it automatically.

In addition to solutions from the previous answer, you can use crates such as downcast_rs to add downcast methods to your trait conveniently.

1 Like

Thank you both so much! That was very helpful, and you both inadvertently hit a number of things I tried that also didn't work out in your answers. :sweat_smile:

You can't put Chunk::as_any directly

I think I tried this at one point, but tried to implement it directly (as a default implementation) and basically hit the same issues with the cast; your suggestions make much more sense.

Or you can reimplement Any::downcast_ref on dyn Chunk , but that's subtle and requires unsafe.

Yeah, I looked at how Any was implemented before posting the question, and the amount of unsafe makes me want to very much leave that to std. (I went with your AsAny approach.)

&chunk has type &&'a dyn Chunk

Yeah, that didn't seem like it would work when I wrote it; if you remove just the & from chunk you get the non-primitive cast error, which suggests,

help: borrow the value for the cast to be valid

which is why I added that. (Even though it didn't make sense, since chunk by itself was &dyn …)

Thank you also for the link to downcast_rs.

1 Like

Note that downcast-rs basically codifies the AsAny pattern, and provides casts for other pointer-like types, so it will be nicer to use than a hand-rolled AsAny. I just wasn't aware of it when I wrote my answer!

1 Like

One has to be aware that this AsAny approach comes with a little side effect. Now one needs to distinguish between p.as_any() and (&*p).as_any():

use std::any::Any;

trait AsAny {
    fn as_any(&self) -> &dyn Any;
}
impl<T: Any> AsAny for T {
    fn as_any(&self) -> &dyn Any {self}
}

trait Object: AsAny {}

struct Duck {}
impl Object for Duck {}

fn main() {
    let p: Box<dyn Object> = Box::new(Duck{});
    assert!(p.as_any().is::<Box<dyn Object>>());
    assert!((&*p).as_any().is::<Duck>());
}

1 Like

That's only if you have AsAny is scope, otherwise there is no difference

mod as_any {
    use std::any::Any;

    pub trait AsAny {
        fn as_any(&self) -> &dyn Any;
    }
    
    impl<T: Any> AsAny for T {
        fn as_any(&self) -> &dyn Any {self}
    }
}

trait Object: as_any::AsAny {}

struct Duck {}
impl Object for Duck {}

fn main() {
    let p: Box<dyn Object> = Box::new(Duck{});
    assert!(p.as_any().is::<Duck>());
    assert!((&*p).as_any().is::<Duck>());
}

For those who are curious, the reason for this distinction comes down to how Rust does method resolution using the dot operator. Given some expression value.method($(args),*) where value: T Rust first checks if T has a method method (including from traits that are in scope). If that fails, it checks &mut T, &T and does the same. If that fails, it checks <T as Deref>::Target (recursively through all derefs) and checks if they have a method method on them. If this finally fails, then you get a compilation error. (there are some more subtleties, and I'm not quite sure about the order, but this is a rough sketch of how it works)

This is quite magical, and can result in some counter-intuitive code in some cases, but in most cases it Just Works.

In the case @Finn points out, we see that Box<dyn Object>: AsAny, and since AsAny is in scope, it gets picked by method resolution. However, if we explicitly reduce to &dyn Object, Rust can see that dyn Object has a method as_any ("inherited" from AsAny), and Rust uses that.

In my case above, AsAny isn't in scope, so Box<dyn Object>: AsAny isn't valid (Rust can only pick traits that are in scope), so it goes through the Deref chain and gets to dyn Object. Now, my updated version can still be fooled, just add the impl

impl<T: ?Sized + Object> Object for Box<T> {}

And now it will always matter if you deref first or not. This is just something you need to consider when you are writing code relying on AsAny. The only solution to this is write comprehensive tests to make sure that you aren't falling into this trap.

This footgun also exits with downcast-rs (because it just does the AsAny trick). So you will need to be careful with that too.

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.