Actually, I want to give a complete analysis of this later.
Tips : I believe there are 4 valid answers... No I think there is only 2.
Another tips: I cannot write
fn func(a: &'a i32, b: &'b i32) -> &i32;
as the starting point, because you have to have the lifetime declared in the generic parameter part <>. But the generic part is where you will play the magic...
Sure, you did after all write "illegal" function signature, so it should not be a problem to leave out the declaration part But since you weren't after @Jeffrey04's answer, you're probably after different lifetimes for a and b, and should probably state that so beginners don't get confused.
Ok, I just thought of the remaining 2 options, so you're right not to demand different lifetimes for a and b.
The answer that @Jeffrey04 gave above should be the answer you want to see - it's the canonical way to represent the situation (although using &i32 is not very useful - &str would be better, for example).
I'm afraid the other valid combinations you're going to propose/elicit are only going to confuse beginners because there's no reason to use them here. If you want to demonstrate more "exotic" generic lifetime bounds, it's always better to show them in the context where they're actually required, and then explain why other approaches don't work.
I took this as an exercise to demonstrate unusual (not necessarily idiomatic or correct) usages of life-time constraints to see some interactions in the various ways of declaring lifetimes and how various combinations can end up equivalent.
That's what it ends up being, but it shouldn't have "for newbies" in the title then . And I don't think it's a useful learning/illustration example because it's just more verbose ways to do the same thing that can be accomplished in canonical and shorter code.
A newbie reading this doesn't learn anything from the more elaborate bounds - they wouldn't necessarily figure out how to take this example and translate it to a situation where those types of constraints are actually necessary to pass borrowck.
Sorry, I don't mean to be a downer on this - I appreciate @earthengine trying to be helpful to folks here - but I feel that this effort can be better spent on proper illustrations/examples.
Although I mostly agree I do see one way in such a demonstration can be useful for newbies, if we use the canonical solution (leaving aside that you may as well dispense with references in this case):
When you try to explain what is going on here a newbie will tend to think in terms of, "lifetime 'a exists, lifetime 'b is longer than lifetime 'a, the thing we're returning has lifetime 'a so how could b ever be the return value". They don't immediately understand that 'a : 'b is equivalent to 'b : 'a in this case and have trouble understanding that the return value must simply not outlive a or b. That's all the declaration says.
With the 3 lifetime solution and where clause, it is apparent that the thing being returned has to outlive both a and b in all cases because it is explicitly stated. Once they understand that, I believe it could be easier for them to then understand the simpler, more idiomatic version and why that simpler version is also correct.
That being said, I'm not sure that that analysis is correct as far as how a newbie might see this.
Thank you for you fantastic analysis! You saved me a lot.
I just want to say how I come up with this. Recently in my projects when I started to twist the lifetimes, I found my self have to decide the "longest" or "shortest" lifetime (more often) from the two input values, to make sure my generic functions as generic as it can be.
The thing I want to express to newbies is, when given any two lifetimes, there would always exist a lifetime that are greater/less then both.
For experienced users though, I also want to say something: the 3 lifetime form is the most generic form. Code that allowed by any other annotations, are subsets of it. Although I don't have proof yet, in theory for any function signature there should exist exactly one such a form.
The reason we do not infer this form automatically, is not because it is not possible, it is for function signature readability.
For the language, I think we should think of some language level facility to express the LSB lifetime of a type - in general, a type can have multiple lifetime parameters, but if we can talk about the shortest one, we are talking about the guaranteed scope that will ensure the type is valid, which will be very useful in generics. Right now, this example is a demonstrate of how to achieve this in today's language.