Why is the implementation of the Trait a placeholder "leaks"?

use std::marker::PhantomData;

struct A<'a,'b>(PhantomData<&'a ()>,PhantomData<&'b ()>);


fn test<F>(f:F)
where F: for<'a,'b> FnOnce(&'a A<'a,'b>){}


fn func1<'a>(v:&'a A<'a,'a>){
   
}
fn main(){
    test(func1);
}

In this example, the compiler says:

implementation of `FnOnce` is not general enough
`for<'a> fn(&'a A<'a, 'a>) {func1}` must implement `FnOnce<(&'0 A<'0, '1>,)>`, for any two lifetimes `'0` and `'1`...
...but it actually implements `FnOnce<(&'2 A<'2, '2>,)>`, for some specific lifetime `'2`

According to placeholder leak

Does func1: for<'a,'b> FnOnce(&'a A<'a,'b>), the document says that replace 'a and 'b to '0 and '1, that is FnOnce(&'0 A<'0,'1>), and associate the trait with the implementation, assuming the implementation would be something like this:

impl<'a> FnOnce<&'a A<'a,'a>> for func1{/*...*/}

then we can get '0 == '$a and '1 == '$a, hence, the taint set for '0 is {'0, '$a}, and the taint set for '1 is {'1, '$a}, the match should success, why does the compiler say the implementation is not general enough? Is there something about "leak" I misunderstood?

I don't think you need to do complex reasoning about the type system in this situation. There is an explanation that seems to me a more straightforwards way to understand what the compiler is telling you:

fn test<F>(f:F)
where F: for<'a, 'b> FnOnce(&'a A<'a, 'b>){}

test accepts a function whose A argument can contain two independent lifetimes, which can each vary independently. That is, 'a and 'b can each be set to any lifetime whatsoever: 'a may be shorter, longer, or the same as 'b, and either may be 'static or any lifetime shorter than that.

fn func1<'a>(v:&'a A<'a, 'a>){ 
}

func takes an A argument whose two lifetime parameters may be anything at all, but must be the same. That is more constrained than what test requires, so test cannot accept this function.

test can, however, accept a small variation on func1:

fn func2<'a, 'b>(v:&'a A<'a, 'b>){
   
}

This signature allows the lifetime arguments to the A argument to vary independently, which matches the requirements imposed by the signature on test. Alternately, test can be modified to accept func1 as-is:

fn test2<F>(f:F)
where F: for<'a> FnOnce(&'a A<'a, 'a>){}

The compiler doc you linked is a gist of the idea written in English with limited examples, but I believe the key part is

a leak is basically any attempt to relate a placeholder region to another placeholder region, or [...]

and if I understand their explanation of the algorithm for this example (which is not for certain), you would start with two taint sets {'0, 'a} and {'1, 'b} which would end up being {'0, 'a, 'b} and {'1, 'b, 'a} because 'a == 'b is a requirement to call func1.


But I also agree the example can be understood without referring to the compiler implementation. The bound on test requires working with all lifetimes such that 'b: 'a, and that includes when 'a is not equal to 'b, but func1 only implements the trait when 'a == 'b.

2 Likes

Yeah, it's mildly important to call out that there's a WF obligation of 'b: 'a here in for<'a,'b> FnOnce(&'a A<'a,'b>).

Where does that 'b come from? The linked doc says we should first Replace bound regions in the obligation with placeholders., that is, the 'a and 'b in the obligation are replaced by '0 and '1, respectively. The second step is: Match the impl against the placeholder obligation.

So, with this hypothetical implementation:

impl<'a> FnOnce<&'a A<'a,'a>> for func1{/*...*/}

The region variable introduced in the implementation, by the manner of the doc, is '$a, which corresponds to '0 and '1(i.e. '$a=='0 and '$a == '1), so the taint set of '0 is {'0, '$a, '1} and of '1 is {'1, $'a, '0}, these set does not solely contain itself and region variables, because '1 in the set of '0 and '0 in the set of '1 is not region variable, hence the compiler report that error?

You're right, it can't start out with 'a and 'b like I said.

I think your analysis is correct.

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.