[No Audio]
Welcome back again in the new tutorial. As
announced in the last tutorial, this tutorial
is specifically designed to answer the
question that is, why the ownership is being
transferred or moved in case of non-primitive
types such as strings and vectors? To answer
this, we will be looking at how the programs
are being handled inside the memory, and how
they use and interact with the memory of the
computer system. Memory is one of the most
important and crucial resource on our machine
or computer system, and it is always good to
know the architecture of the memory, that is
the way operating system manages the memory,
and the way memory is accessible to us as
programmers. The memory that is assigned to a
program or application in a typical
architecture can be divided into four
segments. One segment of the memory is
assigned to store the instructions that need
to be executed or in simple word, the program
instructions and code. Another section
stores all the Static or Global variables,
these variables have the whole lifetime of a
program application and are accessible
anywhere during the whole lifecycle of the
program, as long as it is in execution.
They are visible to all parts of the program, and
are declared before the start of the main
function at the top of the program and are
typically constants. The third section of the
memory, stores all the information
regarding the function calls and the local
variables defined inside these functions, and
in the context of Rust, all the primitive
type variables. The amount of memory that is
set aside for these three segments, that is
the Text segment, the Global variable segment,
and the Stack does not grow while the
application is running. Let us first
understand, how these three segments of memory
are used when a program executes. We will
come back to the Heap in a while. Suppose we
have this simple program in Rust, we have one
Global variable in this program called
MAX_VALUE, then inside the main
function, we are defining two variables
called x and y to the values of 2 and 4 respectively.
Next, we are calling the
function square_num with these
two values as input, which calls another
function called square and passes to it the
summation of the two values which is
computing the square of the passed summation value.
Let us see what will happen to the
memory when this program is being executed.
When the program is being executed, the three
pieces of memory will be allotted to it
consisting of the Code or Text part, the
Static or Global portion and the Stack portion.
Since the Text part will most
probably not change during the execution of
the program, so we will not explain its usage,
and I will only show up the diagram that
contains the two portion of the memory.
Now let us see what happens to these two parts
when a program executes. Okay, when the program
starts executing, first the main function is invoked.
When the main method is invoked, some
amount of memory from the Stack is
allocated for execution of main.
Since we have one Global variable in this code, so this
variable will reside in the Global section of the memory.
The amount of memory allocated in
Stack for execution of main function, can also
be called the Stack Frame for the main function.
All the variables, arguments to
different functions, the main is calling and
the information where the function should
return back to, all this information is stored
within the Stack Frame. The size of the
Stack Frame for a method or for a function, is
calculated when the program is compiling.
Let me add the names of the variables x,y,
and the name of the function main to this
Stack Frame. Now when the main calls
square_sum function, then the Stack Frame
is allocated for the call to square_sum.
Let me draw a line to
indicate the Stack Frame. All that local
variables of this functions including the
variable of num1 and num2, and the
result will be assigned to memory from this
particular Stack Frame. So I will add to this
Stack, the local variable and the function name.
Next, when the square_sum
calls the square function, again another Stack
Frame will be assigned to the square function,
which will store its local variables, which in
this case is the variable num.
Let us make a new Stack Frame and add the
relevant variables of the function to it.
Now the important point to note is that, at any
time during the execution of the program, the
function at the top of the stack is in execution,
and the rest are kind of paused or in
waiting condition. They are waiting for the
function above to return something, and then
it will resume their respective execution.
Now as soon as the square function will
return and complete its job, it will be
cleared from the Stack memory, and now
square_sum function will resume. I will delete
it from the Stack, so that we have an exact
idea of what is happening. Next, when the
square_sum function ends, its memory will be
released. So let me update the memory.
Finally, when the main completes, the Stack
will become empty, and at this stage, the
Global section of the memory will also become empty.
Okay, here is an important point, when
our program starts, the operating system
allocates some amount of reserved space,
let's say 1 or 2 MB depending on the
operating system. The actual allocation of
the Stack Frames and the
actual allocation of the local variables
happen from the Stack during runtime. And if
the Stack grows beyond the reserved memory
for the Stack for example. So in that case,
this is called Stack Overflow, and in this
case, our program will crash. One common case
of Stack Overflow is, when you keep on
declaring variables inside an infinite loop.
Since we have not covered loops yet, so it
will start to make sense in the next section.
All data stored on the Stack must have,
unknown fixed size, data with an unknown size
at compile time or a size that might change
must be stored on the Heap instead.
You may have guessed correctly that, when we
we are working with strings or vectors, they must
be stored in the Heap. The Heap is less
organized in the sense that, things are not
added to the Heap in sequential order just
like we added them in the Stack. When you put
data on the Heap, you request a certain
amount of space. The operating system finds
an empty spot in the Heap, that is big
enough and marks it as being in use, and
returns a pointer which is the address of the
application. The involvement of operating
system in case of stay allocation is minimal.
The involvement of operating system
creates extra overhead in case of Heap.
Although there are many details as to how the
Heap is actually implemented, but from the
programmer perspective, the easiest way to
comprehend it, is to consider it as a free
large pool of memory or free store of memory
that is available for us to use. Let us see
how our program will store a simple string
inside a Heap. I will add one more block to
the present the Heap memory. Now consider
this simple program, where we have one integer
variable and one string variable. Next, we
are moving the value of s1 into s2,
which will invalidate the variable s1 as
we have pointed out in the previous tutorial.
Afterwards, we have another variable which is
s3, which is basically a reference to s2.
And finally, we have a variable of s4
which is a clone of s2. Let us see how they
are manipulated inside the memory, or in other
words inside the Heap. When this program
executes, we will have a Stack Frame that is
being allocated to the main function. The
value of variable x will be first pushed onto
the Stack Frame, then we encounter the
variable s1, which is a string variable.
Since this variable can grow in size,
therefore, we need to allocate it memory from
the Heap. The operating system will request
to assign memory for the string, that is large
enough to store the entire string. Once the
memory is assigned, a pointer to that
particular memory location will be stored
inside the Stack. Now this means that, the
variable s1 is a pointer which is stored
on the Stack, but its contents are stored on the Heap.
To fetch the contents, we will be
following the pointer in memory. Now in
contrast to some other programming languages,
if we assign another pointer to s1, that is
s1 is pointing to some other memory
location, or s1 is no more valid, then the
previous values in the memory will remain
until the programmer explicitly used some
command for freeing up the Heap
space, that is being allocated to it.
If for some reason, the programmers are unable to
free this free space, for instance, they
forget to free it by writing explicit
commands, then that memory location will
become inaccessible for our operating system
to be used for some other purposes.
The operating system will keep on assuming that
the memory is being used by the program, while
there is no variable in our program, which is
pointing to that particular memory location.
Rust, on the other hand, has a very
different approach, and it deallocates the
space as soon as the variable goes out of scope,
and will not allow this to happen.
Now let us see what will happen when the
next line gets executed. This line will
transfer the ownership by moving the value
into the variable s2. What basically
happens behind the scene is that, the Rust
will delete the pointer s1. Let me delete
the pointer s1 from the Stack, and we'll
create another pointer called s2, which
will point to the same resource in memory.
Let me draw this.
[No Audio]
Moreover, the Rust will not
make a copy of this string, because making a
new copy will require extra overhead of
consulting the operating system which
allocates the memory. Moreover, by not making a
new copy and ensuring that there is an owner,
it also avoids the danger of having some
memory, which is not being used inside the program.
Next, when we create s3, another
variable to the Stack will be pushed, which
will be pointing to the same resource as that
of s2. Let us draw this for a better understanding.
Finally, the final
statement will create a new copy of the
resource, which will again request the
operating system to allocate a new memory.
In this case now, we will have another variable
s4 in the Stack which will be pointing
towards a new memory block and not to the
value of s2, let us draw this also. You may
have noticed that, by having the concept of
ownership, the Rust ensures
that we use the Heap more effectively.
In particular, we only make a copy when it's
being explicitly asked for. By ensuring that
there is always an owner, Rust ensures the
memory safety. These features will become
more obvious to us as we progress in the course.
That brings us to the end of this tutorial.
The question we started with was,
why the ownership is being transferred or
moved in case of non-primitive types such as
strings and vectors? And we got the answer.
This is because, they are being stored in the
Heap, and using the Heap involves the
overhead of asking the operating system for
allocation of memory, and returning a pointer
to that particular location. Moreover, it
also requires memory safety features. We will
cover the memory safety feature later on in
the course also, in some detail. In the next
tutorial, we will be covering the details of
ownership in the context of functions.
Do come back for that, because we have now
some good understanding and good foundations
to learn more about ownership.
Until next tutorial, Happy Rust programming.
[No Audio]