[No Audio]
In this tutorial, we will be looking at the
concept of Ownership within the context of
Functions. To make sure that the Ownership
rules are fresh in our mind, I have put them
in comments at the start of the program.
Let's recall these these rules once more so
that they become fresh again. The first rule
is that, each value must have an owner.
The second rule is that there is exactly one
owner at any time and the last rule is that,
when the owner goes out of scope,
the value will be dropped.
Let us recall the key
concepts we learned. We looked at the
differences between a move and a copy.
The copy means making a new copy of the primitive
variables in the stack, while a move occurs
when a non-primitive value is being assigned
to another variable. We also explain the
concept of referencing, which is just a
reference and is used to borrow some value
without affecting the Ownership. Lastly, we
looked at the scope, which is indicated in
Rust by anything within curly brackets, which
are also called code blocks or code segments.
When the ending curly brackets are reached,
the scope of the variable defined inside the
code segment ends and it has been dropped.
And if it resides inside the Heap, then its
memory will be returned back to the operating system.
Okay, this was just a refresher of
the last two tutorials in this section. Now
let us start with today's learning agenda,
that is to learn how the Ownership works
within the context of Functions. I will
declare a few variables. The first variable
called stack_num will have a value of let's say 32.
The second variable
called heap_num is a vector
containing some values.
[No Audio]
The first variable in this case now, is a
simple integer, which we know has fixed size
and therefore will be stored inside the Stack.
And the second variable is a vector, which is
of variable size, and therefore it is going to
be stored on the Heap. I intentionally named
them like this to reflect where these
variables will be stored inside the memory.
Next, I will define a function called
stack_function, which is going to accept
the stack_num variable as input.
[No Audio]
Inside the function, we will update the value
of the input. I will also display the contents
of the variable inside the function.
[No Audio]
Inside the main I will call this function
with the stack_num as its input.
[No Audio]
I will add so print, I will also add a print
statement to indicate the current value of
the variable stack_num.
[No Audio]
Now you may note that, the variable stack_num
which is passed to the
function is not mutable. However, inside the
function definition of the stack_function,
you may note that the variable var
is considered to be mutable, and inside the
function we are changing its value to a value of 56.
So what happens basically is, when we
pass a variable to a function which is
stored on the Stack, that is variables of
primitive types such as integers.
So the function will make a copy of that and will
use a copy. The move operation does not take
place in this case. This means that the
updation of the value inside the function
does not affect the original value of the
variable stack_num, and it will
therefore remain unchanged in the main function.
However, the variable stack_num
is immutable, whil the
variable var inside the function is declared
to be mutable. We can do this as they are two
different variables, since they are copies
and represent different things now.
Let us execute this to confirm the result.
[No Audio]
Please note that, even it does not matter if
we use the same variable names in the main
and inside the function. The essential
behavior that we explained will remain the same.
Let me change the variable name inside
the function to, so that it exactly uses the
same name as the variable inside the main function.
[No Audio]
In this case, still the variable stack_num
defined inside the function
will be a different variable from the stack_num
defined inside the main function.
Let us execute to confirm that the behavior
of the program does not change due to this.
[No Audio]
The summary so far is that, when a variable
which is on the stack is passed to a function,
it is simply going to be copied and not moved.
Now let us see what happens to a
variable which is on a Heap is passed to a
function. I will write a function outside the
main which will use the variable on Heap,
this function will accept the vector as an input.
[No Audio]
Inside the function, I will use the
variable in a print statement and will
display its contents.
[No Audio]
Let us call this function from the main.
[No Audio]
Next, let us print the variable.
[No Audio]
You may note that the compiler is complaining
already. Let us see the error message to
have some intuition. it says borrowed of a
moved value. What happens basically is that,
when a variable which is on the Heap is
passed to a function, its value is moved to
that of the function. In this case, the value
of heap_num is being moved to the
variable of var inside the heap_function.
From the Ownership perspective,
this means that the owner of the value is now
changed, and the new owner is the variable
var, which is inside the function.
This variable resides inside the scope of this
function. This means that when the function
completes and finishes, the variable scope
will end, and its value will be dropped.
This is the third rule of ownership which says
that, when the owner goes out of scope,
the value will be dropped. So therefore,
when the function is called and it
completes its execution, so the variable
here will no more exist, and therefore, using
it afterwards will lead to errors.
To summarize the discussion, when a variable
on Stack is passed to a function, a copy occurs,
while when a variable on Heap is
passed to a function, a move occurs.
Now if we intend to not lose the values, of our
variables after the function ends, we can
pass a reference to the variable instead of
passing the value itself. This is also
sometimes called as passing a value by
reference, we can do this by putting an &
before its name in the call to function.
Moreover, we will also need to
mention the appropriate syntax, that is, we
need to put an & sign before the data type in
the Function Parameter List.
[No Audio]
Using this syntax, the ownership will remain
with the heap_num and it will not be transferred.
Let us save this, and you may
have already notice that, the compiler error
message is gone and the compiler is no more
complaining. If we want to change the value of
the variable without changing the ownership,
then in that case, we will be passing a
mutable reference to the function. To pass a
mutable reference to the function, we will
write &mut instead of simple & in
the function call. We will also indicate to
the function that, you will be receiving a
mutable reference by writing &mut
before Vec. Let us update the
variable inside the function by adding one
more value to the vector, I will use the push
function for this purpose, so I will write
something like var.push 50.
[No Audio]
You may note, inside the main function that the compiler is
not showing any error in the print statement,
which means that the variable heap_num
exists, and it is still the owner of the variable.
[No Audio]
In this case, both the print
statements that is inside the function and
inside the main function, will print the same
vector because the vector has been updated
inside the function without changing the
ownership of the variable.
Let us execute to see the output.
[No Audio]
You may note that, both the print statements
are printing exactly the same vector. Okay,
now it's time for a short quiz.
I promise it will not be difficult, and I'm quite sure that
you will guess the answer of this quiz correctly.
[No Audio]
I will first declare a vector called some_vec
with some arbitrary values.
[No Audio]
Next, I will define another variable, and will
assign to it this particular vector.
[No Audio]
Finally, I will add the statement of ref2 = &ref1.
[No Audio]
Now the quiz is to guess the
owner of the vector in the first statement.
I will give you 10 seconds to
think, and then I will give you the answer.
[No Audio]
I believe you would have guessed the correct
answer in this case, it is the ref1
variable, let us see why this is so. The
initial owner is the variable some_vec
because we assigned to it the vector.
The next statement changed the ownership, and
now the new owner is the variable ref1.
The last statement does not change the
ownership, and therefore the ownership remains
with the variable ref1. Okay, there are a
couple of more small points that I want to
clarify before we end this tutorial.
The first point is related to a common mistake,
junior level programmers in Rust tends to do,
when they first encounter the ownership concept.
The mistake is that they
tend to create mutable variable, which is a
reference to some other variable. The Rust
don't allow you to create a mutable variable
which is a reference. Let me clarify what I
mean by this. Suppose I created a mutable
variable vec_1 containing some values.
[No Audio]
In the next line, I want to create
another variable which will hold a reference
to the variable vec_1, but I declare it as mutable.
[No Audio]
Although this code will compile,
but it does not make a strong sense. We
should not be creating a reference which is
itself mutable. Rather, we should be creating
a mutable reference whose syntax is to put
the word mut after the & sign. So you need
to be aware of the correct syntax, and you
should be able to differentiate between
mutable reference and a reference being mutable.
You might be thinking when would
reference become handy, or when we would
like to use them? This is a natural question,
and it is supposed to come to your mind
sooner or later. The references will become
very handy in our programs when we are
dealing with large amounts of data that are
stored in some complicated data types such as
structures. Of course, copying the whole data
will not be effective and will be a costly
operation to save time and resources of the
computer system, we will be using a reference instead.
Let us see an example of this to
give you just an intuition. Suppose we have
two strings in our programs containing some
huge strings, I will create two string
variables called large_data1
and large_data2, and we'll initialize
them from some string values.
[No Audio]
Let us assume for a minute, that these two
variables are representing some large amounts
of data that are being stored on the Heap.
Now one of our program requirements is to
merge these two strings together in a single
variable. There can be two ways in which such
a merge may occur, I will call it physical
and conceptual. In physical type, we will be
actually creating copies of these two large
strings and then assigning a different
variable name to it. This will involve heavy
usage of memory and will not be cost effective.
Another way which I will call
conceptual, is to use references to these
two variables and do not make copies.
Let us see how this can be done. I will declare
another variable for this purpose called huge_data.
[No Audio]
I can now easily initialize this from the
references of the two large string values.
[No Audio]
This essentially provides a conceptual way of
combining the two strings together without
copying their contents again in the memory,
but by rather using the references.
This operation will work very fast, and will not
have any significant overhead to work with them.
Okay one last point before we end,
this is with regards to the stack_function
that we have created earlier.
The function is having a local copy of the
variable stack_num, which does not
exist when the function end, and therefore its
value is being lost. If we intend to use its
value in the main function, and want its value
to be returned back to the original variable,
then we will make use of the return value
from the function. We will define the function
so that it returns a value, and in this case,
the returning value will be assigned to the
variable stack_num in the main function.
Since we since we have already
covered the definition of a function and its
details, so I will keep it for the sake of
the practice for you to see, how the
return value may be stored inside a variable
in the main. That brings us to the end of
this tutorial, we have learned more concepts
related to ownership and borrowing, but this
time within the context of Functions.
I hope you would have enjoyed this tutorial.
We are having some practice problems at the end of
this section of the course, I would strongly
recommend that you go through them and try
doing them immediately. If you delay doing
them, then you may not be able to retain
these concepts. If you do them right away,
then these concepts will get mature in your
mind, and will therefore remain in your mind
for a longer period of time.
See you again with more concepts regarding
ownership, and until next tutorial,
happy Rust programming.
[No Audio]