- In this section,
we're going to see how to return a reference
from a function.
So first of all,
to declare that a function returns a reference,
you use the syntax
here's a function,
it returns a reference to some type.
Inside the function to actually return a reference,
it looks like this.
This function specifies it returns a reference
to some type.
So inside the function
you return a reference to some value of that type.
So quite straightforward,
an ampersand to indicate that you are returning a reference,
an ampersand to indicate
that the type is a reference as well.
Right what about receiving a reference?
When you call a function
and it returns a reference
how can you assign that to a variable?
So here is an example
using either implicit typing
or explicit typing.
So imagine you have a function
that returns a reference.
You can just assign the return value to a variable
and the compiler will implicitly type this
as a reference depending on what type it is, okay?
A reference to a string, for example.
Or alternatively, if you prefer,
you can use explicit typing,
call the function store the value in R
which is explicitly a reference to some type.
Given the choice
I would prefer implicit typing
because it's more concise,
but explicit typing.
You might wanna try that initially a few times
just to convince yourself
that you know the syntax.
Right, now lifetime management Rust is very careful.
It doesn't allow you
to refer to an object of a short lifetime.
So if you have a reference to an object, okay?
But that object might disappear before the reference does
the compiler won't let that happen.
It won't let you have a dangling reference
because obviously that's very dangerous.
So you can't return a reference
to a local stack based object
because at the end of the function,
that local stack based object
is gonna be dropped
and you'll end up returning a reference to something
that is no longer there.
And Rust doesn't allow dangling references.
So here's an example,
this code here will give a compiler error
so inside the function, I've declared S
as a local string object and it contains "Hi".
And then I attempt to return a reference to S.
Imagine the compiler let you do that,
you would return back a reference to that string object.
The problem is
that that local string object
is gonna be popped off the stack at the end of the function.
It's gonna disappear.
And what you've just done
is you've returned back to the main corner,
you've returned a reference
to an object that is no longer there
and the compiler won't let you do it.
You can't return a reference to a local stack based object.
This example also gives me an error,
not quite so obvious
but still it will give me an error.
In this case, I've got a parameter
and that parameter is basically on the stack, okay?
So imagine this string,
I've passed the value into this function
and let's say this string is here
it is still a local variable, okay?
And therefore we'll go out of scope
and be destroyed at the end of the function.
So same problem as before.
So whether you declare a local variable by value
or whether you declare a parameter by value
it's still local and on the stack.
So if you did try to return a reference
if the compiler let you do that,
you'd be returning a reference to a string object
that's about to disappear at the end of the function.
And that would be a dangling reference.
And that is not allowed,
compiler error at this point, right?
So it's all about lifetime management,
basically Rust always needs to know
that what it's referring to will live long enough
so we don't end up with a reference into a void space.
So one approach is to actually create the object
before you call the function
and pass it by reference into the function.
So the variable or the object
actually lives in the scope of the caller.
When the call function returns
the caller function still exists
and its local variables are still in scope.
So let's see an example of this.
So from the top,
I have a string S,
that's my string object up here,
and it contains "Hi".
So far, so good.
I pass a reference to that string object into here.
So this here is a reference
to that string, okay?
And then because it's a reference,
it is a reference I return S is a reference, isn't it?
Yeah so this here is a string reference as well.
So what I'll be returning
is basically a reference to the same string object.
I'll be returning back a reference
to that string object.
At the end of this function
this string isn't destroyed
because this was just borrow.
We haven't got ownership of the string.
We were just borrowing it temporarily.
So the string object isn't destroyed.
And the reference that gets returned back
we put into R
and R, refers to an object that still exists.
Because basically the issue here
is that the underlying string object
was basically defined in this scope, okay?
So we can borrow it here,
we can return a reference to it here
but it's this scope that basically owns the string.
So you're allowed to return a reference to that string
because it's still in scope.
There's one last thing we need to think about
and it's a bit complicated.
Imagine you wanted to return a string literal.
So imagine inside here
I said we return "Hi" like that
or something along those lines.
Now, unfortunately
although that might look quite straightforward
it doesn't work.
This function doesn't compile
basically the problem is the compiler,
basically remember what a string slice is,
it's basically a pointer to some text somewhere
and also the size of that slice
it's a fat pointer like that.
So the problem is, if you return a reference
a string slice like that,
the compiler can't be sure
that this string text will live long enough.
Imagine that text was in a local if statement
it might be destroyed prematurely,
in which case you've now got a slice
that refers to text that is no longer there.
The compiler must be absolutely sure
about the lifetime of that text
so that you can refer to it safely.
So a common approach
is to specify something called a static lifetime parameter.
What you do is you put this syntax
like an apostrophe static.
It's called a lifetime parameter.
And static lifetime
means basically
you're referring to an actual string literal.
A string literal,
remember, something you might declare
as a literal string like that,
is permanently allocated in the static storage
of your application.
So it'll always be there.
So if you declare a string slice like this
what you're telling the compiler
is that you're basically gonna be referring to a text
that will always be statically allocated.
It's safe to refer to that string
because it's always gonna be there
because it has a static lifetime.
And actually you'll see that cropping up
in Rust code quite a lot as well.
So I think we'll have a look at an example
of all these things.
It's lesson eight functions is a project
demo return in reference.
That's the code we're gonna look at
and we're gonna run it.
Let's take a look.
Okay, so here we are on the code demo.
First of all, let's look at the main code.
We'll uncomment the demo that we are interested in
demo return and reference this here.
So I've got a couple of functions
which are kind of well-written,
and then I've got a couple of functions here
that would give me some compiler errors.
So we'll concentrate on the good functions first, okay?
And I think I'll run it,
and then we can kind of step through the results
as we go through the code.
So cargo run.
Okay then, so from the top in demo we turn in reference.
Do it,
Yes, indeed.
Here we are.
So I create a string object, "Hello World"
and I pass a reference of that string into this function.
Okay now,
if you have a function that takes a string slice
you can always pass an actual string reference into it.
So there's an automatic conversion.
I'll just delete that comment for the moment.
I pass in a string reference into a function
that was expecting to receive a string slice.
That's always allowed.
You can take a reference to a whole string object
and pass it into a slice.
It basically points to the text inside the string.
Okay, let me just reinstate that comment for later.
So this basically receives effectively the text
of the string, "Hello World".
Then what I do
is I iterate through all the characters
looking for a space.
As soon as I find a space, I stop.
If it's not a space,
I just increment the pause counter,
pause will tell me the position of the first space.
And then what I do is I return a string slice.
I take my string, my string slice,
the original string slice,
and I get the characters from zero
up to but not including the position of the space.
So basically it'll be everything up to
but not including the space character.
It'll be a slice that basically references that part.
All right, so that's a string slice that I return.
I return a slice into the original string.
R1 is a string slice that basically refers to "Hello".
So when I print R1, it prints "Hello".
Okay, and then I do the same thing again
but this time
I declare my reference variable
as explicitly as a string reference
as opposed to using type inference.
I actually type it explicitly,
but apart from that the example is exactly the same
and therefore it also prints, "Hello".
Very good.
What about this get message function?
Get message, I pass in my exam mark
and it returns with back a string slice.
So here's my get message function.
I pass in my exam mark
and it returns a string literal.
If my mark is greater than it would go to 50,
it returns this string literal, okay?
Remember, a string literal in Rust
is a string slice
and you've got tell it
that the slice is referring to statically allocated text.
If we didn't put the dash static in there
we'd get a compiler error.
It would say I'm returning a reference to a string
but I don't know the lifetime of that text,
It could disappear underneath my feet, help.
So we help.
And return in a reference to statically allocated text.
Okay So in other words, it'll be a pointer effectively
to text which will always be there
such as a string literal,
you either pass or sadly you fail.
Well, the marker passed then was 99.
So the string literal that it returns,
or the string slice
basically it returns a string slice
that points to that text pass.
And that's what we return back up here.
So the get message function
returns a string slice which is pass.
The message is pass.
And again obviously here
I didn't need to do explicit typing.
I could have just used type inference.
It depends whether you prefer explicit or implicit typing.
Okay, couple of functions that won't work.
This bad function number one won't work
because I've got a local string
and I'm trying to return a reference to a local string
a dangling reference that would give me a compiler error.
Let's, and comment that code
and see what happens when I do a cargo check.
So I'm expecting an error on line 35.
Okay oh, actually it didn't get that far
with a string slice.
It needed a lifetime parameter,
like static, for example.
It needs to know the lifetime of the text
that it was referring to.
It even suggested
that I give it a static storage lifetime specifier
so it knows the length
or the duration
or the lifetime of the text that's gonna be returned.
So that didn't work, did it?
That was a bad function.
And here's another bad function.
So similar kind of reason,
but a different actual example.
I've got a local string here
a string that was passed by value.
This string S,
is gonna disappear at the end of the function
so it would be very dangerous
to return a reference into that string
because it's about to disappear.
So this also is gonna give me a compiler error
and it says same kind of thing really,
on line 41,
it really doesn't like the fact
that this string size
doesn't tell me how long the text is gonna live.
I'm expecting some kind of lifetime parameter
to tell me how long the object's gonna live.
Otherwise, I'm not gonna let you return a reference to it
in case it's a short lifetime.
So it suggests maybe adding in a static specifier.
Yeah, so basically the real solution for us in this case
would've be to pass in a string reference.
So instead of copying the original string
and having a string that's gonna go up scope,
we pass in a reference to the outer scope string
whose lifetime is dictated by the caller
which is gonna outlive the return value from this function.
So you have to be careful, basically
when you're returning references,
you have to make sure that you return a reference
to something whose lifetime is long enough
for you to refer to.
Okay so be careful.