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

Hello. I've hit a strange problem when working with the Any trait. I managed to do the following minimal repro:


struct StructWithoutRefs;
struct StructWithRefs<'a>(&'a usize);

fn ex1(s: StructWithoutRefs) {
    let t = s.type_id();
    println!("{:?}", t);
}

fn ex2<'a>(s: &'a StructWithoutRefs) {
    let t = s.type_id();
    println!("{:?}", t);
}

fn ex3<'a>(s: &'a mut StructWithoutRefs) {
    let t = s.type_id(); // ERROR!
    println!("{:?}", t);
}

fn ex4<'a>(s: StructWithRefs<'a>) {
    let t = s.type_id(); // ERROR!
    println!("{:?}", t);
}

The compiler error in ex3 and ex4 is the following:

borrowed data escapes outside of function
s escapes the function body here

Now, I think I understand why the error is happening in ex4. Any is implemented for T: 'static and StructWithRefs will never be . But what I don't get is why this is a problem in the first place.

I have two questions here.

  1. TypeId doesn't have any references so why was Any restricted on 'static? Why can't I simply get the TypeId of the non 'static StructWithRefs type?
  2. For the ex3 case, why would this leak, especially when the same immutable case in ex2 doesn't cause an error?

If I change the 'a lifetimes to 'static it fixes all the errors, confirming that this is indeed related to the 'static restriction. Of course I can't do that.

Anything I can do to workaround this? Especially for ex3, as it's something I really needed along ex2 and was surprised by the error. It stops compiling the moment you call type_id() no matter if you use the result or not.

The first question is the same as being asked e.g. in this thread opened 1 day ago. Any requires 'static because Any allows conversions based on the obtained TypeId’s, and lifetime information (is “erased” at compiletime and thus) not part of the TypeIds, so a carelessly designed Any API without the 'static requirements might, unsoundly, allow users to convert e.g. StructWithRefs<'a> into StructWithRefs<'b> of some different (longer) lifetime.

The second question is answered by looking at how method call resolution works. The “algorithm” described in the linked reference page shows that in ex2, the first receiver type being considered is the type of s itself, i.e. &'a StructWithoutRefs, and that directly matches the &self receiver type of the type_id method for the StructWithoutRefs: Any implementation.

Now in contrast whilst one might expect s.type_id() in ex3 to be able to (implicitly) convert the &'a mut StructWithoutRefs to &StructWithoutRefs, too, in order to match the &self type, the actual order of types being considered is

  • &'a mut StructWithoutRefs
  • & &'a mut StructWithoutRefs
  • &mut &'a mut StructWithoutRefs
  • StructWithoutRefs
  • &StructWithoutRefs
  • &mut StructWithoutRefs

You’ll see that &'a StructWithoutRefs does appear, but only fairly late. This is commonly not a problem, so you can for many other traits or inherent methods call a &self method directly on a &mut Self reference without any problems. But in this case, the & &'a mut StructWithoutRefs that comes earlier prevents that, and the compiler decides to go with this receiver type as it matches &self for the instance &'a mut StructWithoutRefs: Any. Yes, even a type like &'a mut StructWithoutRefs implements Any, just with a where clause that (effectively) restricts the lifetime to 'a: 'static. The compiler doesn’t (and frankly cannot) use such a lifetime bound being failed to be met in order to reject this “candidate” in method resolution, it merely says: yep, the type & &'a mut StructWithoutRefs matches the &self type for the generic impl<T: 'static> Any for T implementation, and after this “successful” method resulution, the method call gives rise to the 'a: 'static bound. Subsequently / down the line the borrow checker will of course catch this bound as problematic in this function and reports the “borrowed data escapes outside of functionargument requires that `'a` must outlive `'static` ” error. Well, unless you add the 'a: 'static bound of course, which makes for a weird function signature (with a generic lifetime being effectively restricted to be equal to 'static), but successfully compiling nonetheless.

With the above observation of how method resolution is relevant in mind, you can actually “fix” ex3 (so that it gets the type id of the StructWithoutRefs type successfully) by manually creating the &StructWithoutRefs as in (&*s).type_id(), or even just by inserting a dereference step and letting the compiler doing the auto-ref (going from *s to &*s, i.e. from StructWithoutRefs to &StructWithoutRefs) by writing (*s).type_id(); crucially, this makes sure that the & &'a mut StructWithoutRefs candidate type never comes up in method resolution, and the first matching type is &StructWithoutRefs.

2 Likes

Thank you for elaborating on the first, much clearer now. The method resolution part is also extremely helpful. I'm glad this is easy to solve by dereferencing to force the intended right receiver. Thanks!

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.