- In the previous section,
we showed how to return a structure by value.
And the example we saw was a builder function,
which created an employee,
and returned it by value to the caller.
And the caller then received
ownership of that object,
and that's quite a simple,
straightforward technique.
It's also possible to return a structure
by reference from a function,
and then the caller can use the reference
that you return.
The reference refers to an object.
The caller can use the object
that you refer to.
Okay.
You have to be a bit careful when you return
a reference that you don't return
a dangling reference.
For example, if you have a a local object,
A local stack-based object inside a function
you can't return a reference to it
because at the end of the function,
it would disappear,
and you'd end up returning a reference
to something that doesn't exist anymore.
Rust doesn't let you return dangling references
because it would be dangerous.
Right.
So here's a common scenario.
I've got some function,
like a hypothetical function,
and it receives a couple of objects by reference.
Now I've called it down here.
So let's start the example down here
in the higher level function.
So I create some objects.
Let's say here's an object,
and here's an object.
Like so.
And I pass those objects
by reference into the function, okay.
So this function receives a reference
to that object and a reference to that object.
Let's see.
And inside my function up here,
what I want to do is,
I want to return a reference
to one of those objects.
The example I'm gonna show
in a minute is a comparison.
Which employee earns the most money?
Whichever employee earns the most,
that's the employee
I'm going to return a reference to.
Okay.
So what the compiler, sorry.
What your code could do
is it could return a reference
to maybe this object.
Maybe this object earns the most salary, okay.
So what you could do
is you could return basically
a reference to that object.
You could return that back to the client code.
So the client code receives that reference.
The client code receives the reference
that you specified.
So it would return a reference to the object,
to the employee that earns
the higher salary like that.
And maybe it then prints out
the details for that employee.
Okay.
So that's the common scenario.
We created the objects down here.
We passed references up here.
It returned the reference
to one of those objects.
The highest paid employee for example.
And then we can use that reference
to output details for that employee.
So that's the scenario we're going to achieve.
It's a little bit more complicated
than you'd probably imagine.
Here's a simplistic implementation,
and it doesn't work.
Okay.
So it seems to syntax wise, it seems okay.
Imagine that I've got some employee object here
that E1 is a reference to.
E1 is a reference to an employee.
So let's say E1 is a reference
to this employee object,
and I've got another employee object.
Now, I tell you what the problem's gonna be.
These objects that I'm referring to
with E1 and E2,
they could have different lifetimes.
Maybe this object externally
was defined inside an if statement.
So it has quite a short lifetime,
and maybe this employee object
was defined in the whole function.
So it has a longer lifetime, okay?
And it's the lifetime that's the problem.
So in my code,
if the employee one's salary
is greater than employee two,
what I'm trying to do is to return
basically a reference to E1.
Okay?
So I'm hoping in this case,
I'm trying to return basically
a reference to that employee object.
So the caller,
the caller function would receive a reference
to that object or but maybe,
it's the other way around.
Maybe the second employee has the highest salary
in which case,
I'd be returning a reference to employee two.
So in that case, the call in function
would then receive a reference
to this employee object over here.
And the problem is, when we come back down here
in our call in code,
the compiler needs to know
how long is this reference valid for?
Or to put it another way, what am I referring to?
Am I referring to an object
that has a relatively short lifetime
or something which has a relatively
long lifetime like that?
And the simple fact of the matter is,
by the time that you returned the reference
back to your calling code,
the compiler doesn't know what happened.
The compiler doesn't know really,
the lifetime of the object you're referring to.
It doesn't know what you wrote up here.
Maybe you wrote it the other way around,
salary less than.
So it doesn't know,
which object lifetime to to go by.
It doesn't know how long this reference
is gonna be valid for
because it doesn't know
the lifetime of the object that you've returned.
So we get a compiler error
if you try to write the function
in a simplistic way like this,
even though it all seems legitimate
it doesn't tell the compiler
enough about the lifetime of the object
that you're returning a reference to.
The function attempts to return a reference
to either E1 or E2,
but they could have different lifetimes
in which case the compiler doesn't know
how long will the reference be valid for,
for that long or for that long.
And the compiler needs to know.
The whole point of Rust is to avoid
this kind of error,
like dangling references effectively.
So the solution is a new piece of syntax
that we haven't seen before.
What you've gotta do,
is you've gotta say
something about the lifetime.
The syntax that I've shown here,
this is called lifetime annotation.
Inside angle brackets and that hyphen,
you have some kind of lifetime specifier.
I've called it A.
I could have called it B.
I could have called it Z.
I've just called it A here.
And it says there is some lifetime involved.
And then what I've done is I've applied
that lifetime specifier.
I've applied this lifetime annotation
to both of these parameters here.
Okay.
What it indicates,
it indicates that they both have
the same lifetime.
Because I've used the same lifetime specifier A,
it says whatever E1 refers to,
it will have the same lifetime
as whatever E2 refers to, okay?
And furthermore,
the return type is also annotated
with the lifetime annotation.
It means that whatever I return,
the return type will be a reference
to either E1 or a reference to E2,
but they have the same lifetime.
So in my main code, I'll receive a reference.
It doesn't matter whether I receive a reference
to E1 or E2
because they have the same lifetime.
So that's a tricky thing.
That's something definitely
that you wanna practice and get used to.
By annotating both of these parameters
with the same lifetime parameter,
I'm indicating to the compiler,
they have the same lifetime or rather,
what I'm indicating is,
when the function's called,
it would better receive,
it must receive references to objects
that have the same lifetime.
So it doesn't matter
whether we return a reference to the first one
or the second one
because they have the same lifetime.
The compiler can kind of envisage
what's gonna happen
when that reference gets returned.
If you want to have a mutable reference return,
it's just an extension of the syntax really.
The key point here is that,
the lifetime specifier is needed really
because we need to indicate the lifetime
of the thing being returned will be the same
as the lifetime of the parameters coming in.
If I explain that from the other direction,
the first parameter, E1 is a reference
to mutable object there,
and it has a certain lifetime.
And the second parameter is also a reference
to a mutable parameter,
and it has the same lifetime.
And the returned reference is also mutable.
Okay.
So let's have a look at the example.
My types .RS is our employee structure.
Main runs the code.
The interesting part is in
demo_struct_return reference.
Right.
So main.
Sorry, my types defines the structure.
Main is going to run the demo.
So let's run it there.
And demo_struct_return_reference.
So I've got a couple of functions,
which are the same as before.
So let me just explain those first.
A build function that returns a new employee
with the data on the stack.
I've got a print employee,
which just takes an employee by reference,
and prints this details.
So those two functions are the same as before.
What's different is my choose employee function.
It receives a reference to an employee,
with a certain lifetime and a reference
to another employee with the same lifetime
as specified by this lifetime parameter,
which I've called A,
but I could have called anything.
The reference that's returned will be a reference
to an object with the same lifetime
as these objects.
Okay?
So the compiler knows what to expect back.
It'll receive back a reference
to something that has the same lifetime
as the thing's passed in.
So I check the salary/
If employee one earns more than employee two,
then turn a reference to employee one,
else return a reference to employee two.
Similar kind of idea for the mutable example,
except here the parameters
are mutable references
to objects with a certain lifetime.
Another mutable reference
to an object with a certain lifetime.
And it returns a reference,
a mutable reference
to an object with the same lifetime.
So the returned employee reference
will have the same lifetime
as the two employees that were passed in.
So the compiler can predict
how long that reference
that's returned will be valid for.
The objects that were passed in
will have a certain lifetime.
I know that the returned reference
will have that lifetime,
so that the compiler can make sure
that we only use the object while it's in scope.
Okay?
And then we just return either E1 or E2.
In my main code,
I create a couple of mutable employees.
I receive back a mutable reference
to either E1 or E2 and then I print
whatever's being returned by reference.
I've called it RI.
Okay.
Reference immutable was the name behind that.
And then I've got another variable here, RM.
Reference mutable.
When I call the second function,
I pass in an immutable reference
to E1 and E1.
I'll get back a immutable reference
to E1 or E1.
Give the person a handsome salary,
and then print the person's details.
I hope this works.
Let's see.
Cargo run.
Okay.
So the first employee earns more.
So Jane.
Jane earns 1,001,
whereas John only earned a thousand.
So comparing the salaries,
Jane earns them more,
so that the object that was returned
by reference was Jane in both cases.
Plus a double salary.
So the key point then is,
when you return a reference,
the compiler needs to know
how long does that object live for,
so that I know that the reference is still valid.
Okay.
And by using this lifetime parameter,
I've said, whatever object gets
passed into the first parameter,
will have that lifetime,
and so will the second object.
So the returned reference
will be valid for the same amount of time
as the incoming objects were valid.
Okay.
So I can use the returned reference.
As long as these objects
were passed in were valid,
then the returned reference will still be valid,
and the compiler can make sure
that I've used the object,
The reference correctly there.
So you might want to do
a little bit of reading around on this concept.
Lifetime annotations.
It is quite tricky, but it's to avoid
some of the more insidious error
that you can get in C++ and other languages
where you have a reference
to something that's no longer there,
it just disappeared.
Rust gives you that kind of a surety
without having to rely on you know,
complicated things like garbage collections,
and reference company and so on.
Okay, so there we go.
Lifetime annotations.