- Memory management in Rust
is quite different from other languages.
It's the one thing
that differentiates it from C++ for example.
In this lesson,
we're going to look at issues such as scope.
What is the scope of a variable?
In other words, where can you see it?
Where is it visible?
Where physically is memory allocated?
When you allocate an object,
is it allocated on the stack,
on the heap, or somewhere else?
And we'll also talk about ownership.
This is a big issue in Rust.
When you assign one variable to another,
you have to worry or think
about who actually owns the object after the assignment.
Rust ownership and borrowing is quite tricky.
So we're gonna take it carefully and step by step
so that you can understand exactly
how it works, and why it works,
and why it makes your programs more robust and safer.
So first of all, local scope.
When you define a variable inside a function,
it has local scope.
It goes out of scope at the end of the function.
At the end of the curly brackets,
the variable disappears and it's gone.
Here's a simple example.
I've declared a variable x inside a function.
I've given it the value 42 obviously,
and the variable goes out of scope
at the end of the function and it disappears.
So it's fairly straightforward.
Most languages had worked this way.
This is the way most variables are declared in Rust,
as local variables on the stack,
and they disappear at the end of the function.
You can also declare a variable inside a block.
For example, inside an if statement.
When you define a variable in a block,
it has block scope and it goes out of scope
at the end of the closing curly brackets.
So let's have a look at this example.
Here, I've got a test.
And inside the test,
I declare a block scope variable s1.
s1 is only visible from here
to the end of the enclosing block,
and it goes out of scope at this point
and you can't access it down here.
Okay, so Rust has proper block scope.
Again, that's not a surprise.
Most languages also work like this.
Where is memory allocated?
Well, when you declare available in the function,
by default it's allocated on the stack.
So when you enter a function, the variable gets created.
When you exit the function, the variable gets popped
off the stack at the end of the function
and it disappears automatically, okay?
So it's possible to allocate memory elsewhere rather
than allocating on the stack.
You can also allocate a static storage.
So for example, a global variable would have static storage.
It exists forever, basically until the end
of the entire program.
It's also possible to declare a variable
inside a function, a static storage.
And you can do this in C and C++ as well.
When you enter a function, the variable gets created
and then it lives forever across function calls,
it's statically allocated and can retain a value
each time you call it,
maybe you can increment the value of a static counter.
So it has a static permanently allocated storage class.
That too will be quite tricky in Rust as we'll see later.
You can also allocate objects on the heap.
This would be equivalent to using new and delete in C++.
So in Rust, this would be the exception to the rule,
allocating on the heap, you tend not to.
If you want to, then you use a structure called box.
A box is effectively a pointer
to an object in dynamic memory.
The box contains the address of the object on the heap.
So we'll have a look at that later as well.
So let's have a look at an example.
First of all, of local scope.
It's in the lesson six scope ownership project.
I created this project using cargo.
And in particular,
each section in this lesson will be a different file.
So for this section,
the file we're gonna look at is demo_locals.rs.
So we look at the code and then we'll run it as normal.
So first of all,
let me just show you the structure of the project.
So if you downloaded the demos for the course,
go into rustdev, lesson06_scope_ownership.
All the demos for this lesson are in here.
So let's open that project
in Visual Studio Code and take a look at the code.
Okay, so here it is.
Now what I've done from now on with my demos
is I've organized my code into separate files just
to give me a little bit more structure.
So for each part of the lesson,
for each separate kind of section,
we'll have a different file, okay?
So at the moment, we're going to be looking at demo_locals.
Let's have a look at that first of all.
I've defined a function.
So demo_locals is a file,
but in Rust you'd call it a module, okay?
So it's a module, which is like a namespace.
Remember, a module is like a namespace in Rust.
And by default when you define functions and structures
and so on, when you define them in a module,
it would be private to the module
unless you say pub, pub means public.
And if you didn't say pub,
then it would be private to this file,
means you could only call the function from this file.
So Rust tries to encourage privacy wherever possible.
If you want to be able to call this function elsewhere
like I'm gonna call it from main in a moment,
then you have to declare this public.
So it's a public function.
I've got a a print statement here just
to introduce the example.
The terminology here.
Demo, I mean, this is just a print statement
but demo_locals is the module name,
which is like a namespace.
And in that module or in that namespace,
we have the do_it function.
It's the do_it function to demonstrate local variables.
I declared a local variable x, 42.
x is non-zero, clearly.
So it'll come into the if block and it'll create s1.
s1 is a block scope variable.
It'll go out of scope at the end of the block.
Okay, so that's the end of s1.
I can't access s1 down here obviously,
because it's disappeared out of scope at that point.
Okay, so I'd get a compile error
if I tried to access s1 out of scope.
Okay, that's simple enough really.
So let's have a look
at where we call this function from main.
So in main, I've got a couple
of things I want to talk about here.
Here's my main function.
I'm gonna un-comment the call, like so.
So from the top, let me just remind you
of a few things we've already discussed.
Let me just remind you about these.
This syntax, when you say hash square brackets,
that's called an attribute in Rust.
And it's effectively kind of like a code directive,
like a compiler directive.
And the allowed dead code means
if I have functions which aren't being invoked
or variables that aren't being used,
then normally I'd get a warning from the compiler
to say why have you not called this function?
If you say allow dead code,
then that allows you to have dead code.
You don't get warnings
if there are functions or whatever that you haven't used.
So in my demo, it'll hide or suppress the compiler warnings.
The exclamation mark here means
that this attribute applies for the entire crate.
And remember in Rust terminology,
the entire project is a crate, okay?
My project is a crate.
And if you say hash exclamation mark
then this attribute applies for the entire crate.
It's the exclamation mark
that makes it a crate level attribute.
So basically allow dead code throughout my entire crate.
And also allow unused imports.
An import is a use statement, that's technically an import.
It's where you are bringing some symbol
into the current scope.
We'll look at this variable later.
Global message will be useful later on.
We can ignore it for now.
But, because I've introduced this variable name into scope,
if I hadn't actually made use of it anywhere in my code,
I'd get a warning to say, "You haven't used this variable."
I'm going to allow unused imports, okay?
So this use statement
where I've imported this name into scope,
allow unused imports, okay, across the entire crate.
So those attributes are going
to stop me getting warnings everywhere,
which is quite nice, really.
And then, so let me remind you about the mod keyword.
So technically what the mod keyword does
is it declares a module.
It introduces a module or a namespace.
And if you have a semicolon directly
after the module declaration, then what Rust will do is
it'll look for a file called demo_locals.rs
and basically drag that code into your application.
So here is demo_locals.
We looked at that file earlier.
Just having the file in your project isn't enough, okay?
You also have to kind of introduce it
into your application crate.
So just the existence of the file,
if you just left it like that
and you didn't have the mod statement,
then it's as if this file will never get imported, okay?
It's as if it was just not there.
So just defined in the file is only half of the equation.
You've also gotta declare the module.
So technically, the main file, main.rs,
declares a module called demo_locals,
causes the compiler to import that file, okay?
So we now have a public function called do_it,
which is in the demo_locals module,
demo_locals::do_it is now available to us.
And that's good because I call it down here.
So in my main code, I call from the demo_locals module,
like a namespace, invoke the do_it function, very nice.
So here is my do_it function in demo_locals,
it's gonna run that code.
Okay, all fine, good.
So let's actually run it then.
When you run the application, it runs the main function.
It looks for a file called main.rs
and it runs the main function in there.
It doesn't matter how many files you've got,
it's the main.rs is the entry point always.
Okay, and we'll call that function.
So let's do a cargo run.
So we're not gonna get any warnings here,
because of our crate scope attributes
that suppresses warnings like so.
It came into my demo_locals do_it function, which is great.
That's the call.
And in that function, it declares s1 and prints s1.
So that's handy.
And that's the end of that.
And if I accidentally try to access s1 here,
I'd expect to get a compiler error,
because s1 goes out of scope and is no longer visible here.
So what I'll do, instead of doing a cargo run,
I'll just do a cargo check.
Remember cargo check, it just does a compile
to check the syntax without actually generating an exe.
So this will give you the compiler errors.
I'm expecting a compiler error on line 13, unlucky for some.
Okay, so in my project lesson06, ownership,
it's complaining in demo_locals line 13.
It's complaining that s1 is not found in scope.
Yes, indeed.
So let's fix that and just check the syntax again.
Fantastic, okay?
So that's a quick introduction to scope.
It leads us into the rest of the lesson
where we can talk about ownership in more detail.