I'm confused by the above example. As shown in the code, the lifetime parameter explicitly specified for the type of b is 'static, the instantiated method should have the function type fn(& A<'static>, &'static i32), obviously, the borrowing of i cannot outlive 'static, however, the call of the method is ok. What's the reason here? If I explicitly call the method with the form
Is it? Wouldn't that mean A::<'static>::call(&b, &i) would work?
I thought this is the doing of subtyping for lifetimes. A reference with a lifetime 'b where 'b outlives 'a is a subtype of 'a. The 'static lifetime is a subtype of any lifetime 'a. So I think this makes A<'a> covariant to A<'static> and therefore allows the compiler to implicitly coerce &'static self to &'a self in the call method's arguments.
If it is, It appears to me the lifetime in the method wouldn't have any restriction that we expect. For example, I would expect b.call(&i); should restrict that the lifetime of &i should outlive the lifetime in A<'a> that is the type of b, However, with the transformation form A::<'_>::call(&b, &i);, the restriction will be gone since the call just need to infer the lifetime to make both arguments coerce to the common type.
References as arguments have to automatically subtype or reborrow anyway in order for the language to be tolerable, so after thinking on it for a moment, this would be the relevant documentation.
Does it mean, for every method call, the call expression receiver.method(...)will be transformed to T::method(&receiver, ...) where T is the general type if it was.
You haven't realized the covariance matters a lot.
The method signature is a contract that says kinda call<'a, 'call>(&'call A<'a>, &'a i32), but
The secret is the covariance on A<'a> after the instantiation.
So consider a stricter contract call_strict<'a>(a: &'a A<'a>, v: &'a i32) which is discouraged though. Rust Playground
If you disregard the point on covariance, the second line would also be a surprise for you:
let mut b: A<'static> = A::<'static>(&0);
call_strict(&b, &i); // Note: this line is not `call_strict(&'static A<'static>, &'static i)`
&mut b; // without the covariance after the instantiation, you couldn't do this
The code compiles fine.
The link shows it also works for A::<'_>(&0) a type with inferred probably non-static lifetime.
What's the trick you're missing or misunderstanding?
Invariance. Consider to confine the power of covariance that would happen after the instantiation: Rust Playground
struct A<'a>(*mut &'a i32);
let mut b: A<'static> = A::<'static>(&mut &0);
b.call(&i); // error: `i` does not live long enough
This time, you're probably not stunned any more. Once you have a A<'static>, the lifetime just stays 'static and everything seems easy to understand. Except for the following working case:
let mut b = A::<'_>(&mut &0);
b.call(&i);
Oops, you haven't confined the power of covariance that has happened before the instantiation
let mut b = A::<'_>(&mut &0);
b.call(&i); // ^
// ^-----------------↑ lifetimes are tied together according to the call method
// but the compiler makes `&'long 0` shorter beforehand
The secret is the covariance on A<'a> after the instantiation.
The secret is b.call(&i) is not equivalent to A::<'static>::call(&b, &i); even though it looks like we want to call method call on the instance b that has type A<'static>, instead, the method call is equivalent to A::<'_>::call(&b, &i), that is, the compiler will infer a lifetime for '_ such that 'static:'_ and 'i: '_, then use the inferred lifetime '_ to instantiate the method, then covariant happens after this instantiation.
The transformation is only used to think of how the lifetime will be inferred for a method call. In other words, in a method call expression, the lifetime parameter of the enclosing type in the method signature is not designated by the receiver expression, the lifetime instead should be re-inferred together with other arguments in order to find out the common lifetime they can be coerced to. Is this understanding right?
Seems that way. I think all lifetimes are replaced with inference variables and then checked against constraints.
Concrete lifetimes don't matter for method resolution either... effectively anyway. (I don't think there's a way to implement for a non-overlapping pair of lifetimes, technically, so it must be this way.)
I linked to an existing issue (which I also commented on), but PRs have a better hit rate, so probably I should try that at some point. Although I've seen those stall out too sadly.
That would be better, however, Rust is a high-speed evolve language, so it may be too early to have a standard specification. Anyway, it would be meaningful if there was a book that can commonly interpret borrowing checker, lifetime, coercion, and some obscure concept.