# A little lifetime exercise (for newbies)

Challange: replace the question marks in the following function signature:

``````fn func<?>(a: &? i32, b: &? i32) -> &? i32
``````

with someghing that does not contain the `mut` keyword, to make both

``````{
a
}
``````

and

``````{
b
}
``````

are valid definition body of the function.

There will be two answers, one is easy and one is harder.

Edited for better wording.

4 Likes

use the same lifetime for all of them?

``````fn func<'a>(a: &'a i32, b: &'a i32) -> &'a i32 ...
``````

One valid answer. But not the one I want to see

hmm, then that means the answer is something I donâ€™t understand about lifetime yet (is pulling hair on the exercism puzzles that involves lifetimes)

Did you mean to use

``````fn func(a: &'a i32, b: &'b i32) -> &i32;
``````

as the starting point?

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.

There are over 4 valid, along a very similar stream. (No comment about useful.)

Now you have changed the question will post now rather than later what is to be likely best for all to use;

``````fn func(a: i32, b: i32) -> i32
``````

It is similar to the clippy

Well, your code didnâ€™t have the `&` symbol, it shouldnâ€™t count as I only mention to replace the â€śquestion markâ€ťâ€¦

But wait, I think I have to limit the use of `mut` otherwise the number of answers is uncontrollableâ€¦

Hmmâ€¦are you looking for:

``````fn func( a: &'a i32, b: &'b i32 ) -> &'c i32 where 'a : 'c, 'b : 'c
``````
2 Likes

Yesâ€¦ This is it.

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.

4 Likes

This is what came to my mind, similar to @gbutler69â€™s solution but more strictly holding to â€śonly replace the `?`sâ€ť:

``````fn func<'a: 'b, 'b>(a: &'a i32, b: &'b i32) -> &'b i32
``````

But variance makes it equivalent to the first solution, so `'b` is just noise here. The â€śthree different lifetimesâ€ť solution is no better.

I also thought of another family of solutions, along these lines:

``````fn func<'a>(a: &'a i32, b: &'static i32) -> &'a i32
``````

This could actually be a useful thing to do on rare occasions, like if `func` does something like caching `b` behind the scenes.

1 Like

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.

2 Likes

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):

``````fn func<'a> ( a: &'a i32, b: &'a i32 ) -> &'a i32;
``````

or the less idiomatic solution:

``````fn func<'a, 'b : 'a> ( a: &'a i32, b: &'b i32 ) -> &'a i32;
``````

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.

4 Likes

More examples along those lines would be useful. Again, just IMO.

1 Like

@gbutler69

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.

hmm #offtopic so this forum is more active at this hour eh~

Edit: the discussion is interesting to a newbie like me (:

It seems to vary quite a lot, but, I do see a lot of early morning (WRT US/NewYork TZ) activity.