@gbutler69
I was already thought about this. From the caller's view, they want to know from a signature is:
- what I can pass to the function?
- what I will get from calling this function?
A "generic" signature will ensure the answer of question 1 requires minimal assumption, to allow the best flexibility, and answer of question 2 requires maximum guarantee, to make sure the caller don't abuse the return value incidentally.
So to infer a proper lifetime for your example, let's answer those questions.
The answer to the question 1 is obvious - two input values a
and b
must have different lifetime, otherwise the user will suffer from not allowed to call the function with some specific scopes.
Now to the question 2. If you have a struct with more than one lifetime in bound, the only case that would make a difference compare to having all lifetimes the same is when you want to enable partial move.
Note the inability of partial move is a guarantee, not an assumption, so we are free to reject this. This means we can use a same lifetime for both output components.
Needless to say, that the output lifetime should not outlive the input lifetimes. So lifetime bounds are necessary for both input. Again, this limits the thing a caller can do with the return value, but this is a guarantee.
So, the conclusion of the "most generic" life annotation of your example is:
fn foo<'a: 'c,'b: 'c,'c>(a: &'a i32, b: &'b i32) -> (&'c i32, &'c i32) {
...
}
of cause, if you do want to enable partial move,
fn foo<'a,'b,'c,'d>(a: &'a i32, b: &'b i32) -> (&'c i32, &'d i32)
where 'a:'c, 'b: 'c, 'a:'d,'b:'d
{
...
}