- In the previous section, I introduced you
to the box structure and box acts
as a smart pointer to an object on the heap.
So a box has a pointer to an object on the heap like that.
When a box object goes out of scope,
the heap object is deallocated automatically.
So the drop function on box
will deallocate the object on the heap
and then the box object itself is popped off the stack.
So it's a bit like auto pointer in C++,
in the original C++ upgrade.
So there's also a type called Rc
in Rust, reference counted pointer basically.
It allocates an object on the heap
but it also maintains a reference count.
So an Rc object will have a pointer
to an object on the heap, which is dynamically allocated.
And effectively, it also has a reference counter like so.
This is similar to, very similar indeed to shared pointer
if you're familiar with that in standard C++.
So you can clone an Rc.
Rc implements the clone trait
and you can imagine what the clone function does.
It's like the copy constructor for shared pointer in C++
if you're familiar with that.
It increments the reference counter.
So if you say Rc1 equals Rc2,
then this Rc, this is Rc1, and this is Rc2,
it'll point to the same place,
it'll point to the same reference counter
and the reference counter gets incremented to be two.
When an Rc object goes out of scope,
its drop function is called,
which is like a distractor in a shared pointer.
So let's say Rc2 goes at a scope.
At that point,
it will decrement the reference counter like so.
If the reference counter was zero,
then the underlying object would be deleted.
It isn't zero.
So the object still exists on the heap,
but when Rc1 goes out of scope,
it'll document the reference counter to zero.
And because the reference count's zero,
it means I'm the last surviving owner
of the object and the object itself is deallocated.
And then obviously, the Rc goes out of scope.
So it's a reference counted smart pointer,
just like shared pointer in C++.
So there we go.
Let's have a look at an example in the usual project.
We'll have a look at main.rs
and then we'll have a look at demo_rc and then we'll run it.
Okay, so in main, let's uncomment the code for this demo.
And here is the demo.
I'm going to run it first
because you need to see the output
for the code to make sense.
So what I'll do is I'll step through the code
and I'll kind of explain the output step by step.
So first of all,
I've imported the Rc type from the std::rc module.
I define a simple Employee object structure.
That's the object I'm gonna allocate on heap
via a reference counter.
So you say Rc::new, and then you give it the object,
an Employee and that object gets allocated on the heap.
And the reference counter, that is one initially.
So if I output the Rc structure has an associated function,
static method basically.
Strong_count, interesting term, isn't it?
Strong_count.
That suggests maybe the concept of a weak count as well.
Anyway, the strong_count
is how many references do we have to the object?
What's the current reference count?
The current reference count initially is one,
which is what we'd expect.
So then I call the clone method Rc::clone
and I give it my object.
And notice, it's a static method on Rc.
You don't say Employee object.clone.
Obviously, you're not cloning the object,
you're cloning the reference.
So when you clone the reference count on a,
then output the reference count.
The reference count now is going to be two.
Now then, I've passed my reference count into a method,
a function by reference.
Reference count, so the Rc structure type
doesn't implement copy.
So if I just pass this like so,
then it would move the reference count
into the function into here
and this function would then own the reference count.
It would mean I couldn't then use the reference count,
the Rc object afterwards.
So we don't want to do that.
You don't want to move the object.
I'm going to pass it by reference.
The alternative would be to clone the reference count again
in the function, but I'm gonna pass by reference.
So when I pass by reference like so,
rc_emp will be a reference to a reference count,
which internally points to an employee.
And inside here, I do actually clone,
I clone my parameter, okay?
So that, when I then output the reference count inside here,
the reference count will be incremented.
So that's value three.
Right, now this cloned reference count is local
and it'll go out of scope at the end of the function.
When it goes out of scope, it'll be dropped.
And the underlying reference,
the underlying thing that we're referring to
will have its counter decremented.
So when I come back up here
into my kind of driver code, after the function returns,
the reference count is backed up to two as you can see
in this piece of code here.
And then I've got a block scope.
In that block scope, I create a reference count,
another reference count, a clone,
so it'll bump the count back up to three.
But obviously, it's a local variable,
a block scope variable.
So it goes at the scope at that point
in which case the reference count comes down to two again.
So if I output the reference count here,
it'll go down to two.
Okay, so it's very similar to shared pointer.
You can use this where you want to pass objects around
and you're not sure
if you know when the time's ready to deallocate the object.
You leave the reference counter, keep count
and when the last surviving reference count disappears,
at that point, the object will be deallocated.
Okay, so there we are.
This Rc class is basically the same
as shared pointer in purpose in C++.
If you're familiar with C++,
you'll also know that as well as a shared pointer,
there's also a smart pointer called weak pointer
and so there is in Rust.
There's a structure called weak.
So weak is a version of Rc,
which holds a non-owning reference
to the managed allocation in order to,
it's basically a kind of like a...
It knows which object you're referring to,
but it doesn't participate in reference counting.
So it's a weak pointer.
In order to use a weak pointer,
you have to upgrade the weak pointer
into a proper shared pointer if you like.
This is a bit like when you log a shared pointer in C++.
So the weak pointer has an upgrade mechanism,
which allows you to say, I want now to convert myself
into a strong reference count, which will participate
in the actual reference counting mechanism.
So the reason for using weak pointers,
if you have a shared pointer and you want to observe it
but you're not ready to actually use it yet,
you can use a weak pointer
and you can also use a weak pointer
to avoid circular references.
An example of that would be
in a binary tree where you have a parent which points
to its child and the child points back to its parent, okay?
They can't both be Rcs
because they would form a closed circuit
and they would never release each other
because they refer to each other.
So the parent might hold a strong pointer,
an Rc to its child, and the child might have a weak pointer
to its parent so that we don't get this kind
of circular loop of strong pointers.
There we are. I'll mention one other thing.
The Rc type can't be used in a multithreaded environment.
You can't send an Rc into a thread.
Technically, there's a trait called send
and only if types implement send
can they be passed into a thread.
So it's not thread safe.
There is another related class called Arc,
atomic reference count, and that's thread safe.
Just like we have i32, which isn't thread safe
and then we have atomic i32, which is thread safe.
It locks itself when you increment
and decrement and so on.
Similarly, Rc isn't thread safe, but the Arc,
Arc, atomic reference code is a thread-safe version
of Rc and that's what you should use
if you want to pass it into a thread.
Very similar API.
It's just that it automatically performs lock in
when you use it.
Okay, so have a look at Arc
just to round off your understanding of how this all works.