- In this section,
we are going to talk about how to pass references
into a function.
First of all, a quick recap of what we've seen already,
passing by value.
In this example here, I've got some function.
It takes an i32 by value.
When we pass in an integer 32,
a bit copy occurs because i32 implements the copy trait.
So that's good.
The function also receives a string by value
and that's a little bit more problematic
because string doesn't implement the copy trait.
So if I have a string up here, s and I pass it in,
I move ownership from s into sparam.
So this variable up here loses ownership of the text.
It donates ownership to the function.
Sparam goes at a scope,
the end of the function, the contents of sparam disappears
and then when we come back up to our original function,
we can't use s anymore.
So pass in non-copy types by value, probably not a good idea
because you lose the object
as soon as you pass it into the function.
Okay, so here we go.
For copy types like i32
the value is bit copied into the parameter.
As discussed, the caller retains ownership of n
and can use n afterwards.
For non-copy types, like string, the value is moved
from the original string variable into this one.
You lose ownership of the string up here,
you donate it or you move ownership to here.
But of course, because it's a local variable,
then sparam goes at a scope and the string is dropped.
That's why you can't then use it.
When you come back the object has disappeared.
So in situations like this,
rather than passing copy or non-copy types by value,
you could, and should, consider passing them
by reference instead.
So the called function, the function that you're calling,
receives a reference to the object.
It borrows the object, but it doesn't own it.
The original call in function retains ownership.
Okay, so after the called function returns back to you,
you can still use the value afterwards.
You didn't pass ownership into the other function.
You just allowed the other function to borrow your value.
A reference in other words.
But the original function retains ownership.
This is the main way to pass objects,
non-copy objects into functions.
So how do you do it?
How do you pass a reference parameter into a function?
Well, you just put an ampersand in front of the object
to give you a reference.
Here is an example, here's my call in function.
I've declared a string variable called s, oh,
and an integer called n.
I pass a reference to the integer into some function
and I pass a reference to the string into some function.
Technically the function borrows n and it borrows s, okay?
But the called function doesn't own the values.
When that called function returns,
the original function still owns n and s, okay?
So the called function borrowed n and borrowed s,
it had references to them,
but ownership remained with the original function.
So I can still continue to use, well, specifically,
I can still continue to use s afterwards
because I haven't lost ownership.
So pass in by reference is a key thing to do
when you've got non-copy objects.
So if I pass a reference parameter into a function,
how can I then use a reference parameter in a function?
Well, I'm glad you asked.
In order to use a reference parameter,
you've gotta use a star to dereference it.
So as a C developer or C++,
you might think of this as being more like pointers
than like references.
The syntax is more like pointers than it is like references.
So here's an example.
The function receives a reference
or borrows some kind of i32
and it borrows some kind of string, like so, okay?
So we're basically referring to an external integer
and we're referring to an external string.
So in order to dereference and get the values,
we use the star, okay?
So take my iparam reference
and give me the contents of what it refers to.
The star will give me the contents,
the integer, that it refers to.
I wanna say star sparam.
Sparam is a reference to the string,
star sparam is the contents of that string.
It's a string object.
And then upon the string object,
I can invoke a method to uppercase.
So it would kind of uppercase the contents of the string,
the text, like so.
So it is mixed syntax, it must say, for me,
my original background in this kind of thing was in C++
and this is kind of like a hybrid syntax.
It's different from C++ and you have to recognize that.
So a reference, you declare it as a reference here,
but really it behaves more like a pointer
in the sense that you dereference it
to get back to the object that you first thought of.
And by the way, if you're a C++ developer
and you're wondering if you could rewrite this
as sparam, arrow to uppercase,
you can't, there's no arrow in rust.
You'd have to use the star and the parentheses.
Now, luckily for us, or maybe not,
if you have a reference to an object
and you want to invoke a method on the object,
rust does actually allow you to invoke the method directly.
Okay, so instead of writing it with the star
and the parentheses, you could just write it like so.
It's quite clever the way that rust works internally,
if you have an object
and you want to invoke a method, you can say object.method.
If you have a reference, then you can say reference.method.
It kind of looks at the syntax,
understands what you're trying to do
and treats them interchangeably.
So you don't have to explicitly dereference it like so
and rust developers don't.
Okay, they benefit from the syntax
and the fact that it allows you to invoke a method
using an object
or using a reference using the same syntax.
Well, I've almost finished what I wanted to say
in this section,
but I wanna say just a few more words about strings
because strings are kind of a bit tricky
because there are two different ways
to represent the string.
You can have an ampersand string,
a reference to a string object, okay?
So if you have a function that takes a reference
to a string, you must pass in an actual reference
to an actual string object.
You can't pass in a string slice, right?
So have a look at this piece of code here.
I've got a function that takes a string reference.
We must give it a reference to an actual string object.
We're not allowed to give it a string slice.
Okay, so in my call, up here,
in my main code up here, s is a string object
which points to, 'hello,' like so, so, so good.
I can pass, I can take a reference to s
and pass that into the function.
So that will indeed be a string reference.
That's fine.
Sparam will effectively be a reference
to the string object like so, and that's fine,
but I can't pass in a string literal, okay?
Because remember, a string literal is just, literally,
a piece of text like that.
It's just a slice of text, and that doesn't work.
If the function was expecting a string reference,
it really is expecting a reference to an actual string.
You can't give it a reference to a slice, okay?
Because that's a different beastie.
So that's not so great, really, is it?
If you declare a function that takes a string reference,
you've gotta actually give it an actual string object.
You can't pass in just a string literal, so.
Well there's a solution at hand.
What you do is you actually declare the function
to take a str reference, okay?
So not a string reference, but a str reference.
So a str reference can actually,
you can pass in an ampersand string.
You can take a reference to an actual string object
and assign it into an ampersand str.
To use the terminology in rust,
a string reference decays or can be converted
into a str reference.
So to put it another way,
a string slice can either refer to an actual string
or a string literal.
You can pass in either a string reference or a string slice.
So this function here takes,
and this would be the best way to do it,
this function takes a string slice.
Either an existing string or a string literal.
So let me work through this example.
I'm gonna show you some code to illustrate all this
in a minute.
So from the top, s is an actual string object,
which holds some text,
'hello,' like so, and according to the rules,
and it is just a rule,
if I take ampersand s then that type is string reference.
You are allowed to pass a string reference into a slice.
Okay, so sparam, what actually happens is
sparam ends up being a reference to the text, like that.
The actual text, and plus the length.
Remember that string slices are fat pointers.
They have a pointer and they have the length as well.
So if you passed in an actual string object as a reference
then you will be referring to the text
that the string contains, like so, that is allowed.
On the other hand, if you just pass in literal text,
like 'Joe,' like that, then in that case,
sparam would be a reference into that slice.
So it either be a reference to that slice
or it would've been a reference into that piece of text
inside the string.
So, long story short, if you wanna pass text around
the preferred way to do it is to pass it as a string slice.
That means you can pass in an actual string object
by reference
and you can pass in just a string literal as well.
That's the preferred way to pass strings
by reference into a function.
Right, one other thing,
and then we'll have a look at the example.
If you have a reference parameter
then the default formatter, when you print a reference
it actually displays the value.
It automatically does a star to give you the value
that the reference refers to.
If you want to get back the actual address
that the reference refers to
then you can use the p formatter, the pointer formatter.
Okay, so let's have a look at that.
I've got a reference to an integer
and I've got a string slice, basically.
If I just pass my reference,
the default formatter will print the value of the integer
and it'll print the value of the string.
Or to put it another way,
the default formatter, kind of,
automatically on your behalf,
it automatically dereferences to give you back the contents
of the integer and the contents of the string.
Okay, if you wanted to get back the actual addresses
then you just say :p.
take the reference and print it using the pointer formatter,
take the string and print it using the pointer format.
That'll give you the address that the reference refers to.
So references really are pointers,
addresses under the covers,
but you can output them as actual values
using the default formatter.