Method that does not capture its environment is not static

Hi all,

I am trying to get the type_id of a method within a struct that has a generic parameter:

pub struct Generic<T>(T);

impl<T> Generic<T> {
    fn foo(&self) {
        Self::foo.type_id(); //<~ compiler error here
    }
}

However, this fails to compile saying that T needs to be static. However if I rewrite this using a non-static lifetime rather than a generic parameter, it works fine.

pub struct Lifetime<'a>(&'a i32);

impl<'a> Lifetime<'a> {
    fn foo(&self) {
        Self::foo.type_id(); //all happy here
    }
}

Link to playground

How come when using a struct with a generic field, it needs to be a static type to get the type_id of any method. And how come if I use a struct that has a non-static reference as a field, I am able to get the type_id of the method without any troubles.

It was my understanding that functions/methods are always static since they do not capture any variables from its environment, but this does not seem to hold on this case.

Any help understanding where my gap in knowledge is would be extremely appreciated. Thanks!

Hi there,

I‘ve had a related question some days back and the answer there might shed a light on your question as well:
https://users.rust-lang.org/t/using-typeid-as-key-in-btreemap-lifetime-question-for-typeid-t/36548/4

In a nut shell: if you add the 'static lifetime as a bound to your generic the compiler will be fine.


pub struct Generic<T:’static>(T);

I am aware that adding 'static lifetime to my bound would make this example compile.

In my use case, unfortunately, adding a 'static lifetime to the generic is not doable.

My biggest issue is that I do not understand why then the second example works.

In

pub struct Lifetime<'a>(&'a i32);

impl<'a> Lifetime<'a> {
    fn foo(&self) {
        Self::foo.type_id(); //all happy here
    }
}

My only field is not a static type (it holds a reference, and the lifetime of it is not static) and yet I can get the type_id of the method fine. How come then it requires me to only have static types when I use generics.

Well I’m not an expert in this by any means but I would guess that in your example given with the lifetime as a generic parameter the lifetime bounds of type_id are just requested to be fulfilled by the lifetime ‘a. At compile time without using your Lifetime struct the compiler does not need to check and runs just fine, but as soon as you create an “instance” of the structure Lifetime the lifetime bounds will be checked and complaint by the compiler if the bounds are not met.
That’s at least my understanding. However, I’ve learned that a ‘static lifetime should not be interchanged with variables defined as static.
As I said, I’m also still in the Rust “greenhorn” stage and maybe more lifetime advanced ones can correct me where I’m wrong :wink:

I had the same thought at first but I tried to run it and it worked fine.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ea77d0529155039d6d1ec532f45fbd4b

And no problem! Thanks for looking at it.

You're computing the type id of the type of the function. The difference is that with a generic type, Generic<i32>::foo is a different value than Generic<u32>::foo, but when dealing with a lifetime, there is only one function type. The reason you can call this single function value with any lifetime is that it actually implements the Fn trait multiple times for each different lifetime.

So basically, the generic type is part of the type in the first case, but in the second case, one single value is used for every way it can be called, so the lifetime you called it with is not part of the type of the function.

It works for the same reason that this works:

struct MyFn;

impl MyFn {
    // oh no! our function is generic!
    pub fn call<T>(t: T) -> T {
        t
    }
}

fn main() {
    // but the type of the function is not
    println!("{:?}", MyFn.type_id());
}

playground

Oh... I think I kind of get that.

So regardless of the lifetime, all the methods will have the same type_id so the lifetime can be effectively ignored and my method signature becomes just Lifetime::foo.type_id hence it being "static". In the generic case, however, it cannot use HRTBs so the signature is Generic<T>::foo.type_id, which then requires T to be static.

Is that more or less correct?

Sounds about right.

About right is what I shoot for in life. Thanks! That was very helpful. I've been struggling with this in my mind for way too long.