Can someone explain to me why Rc<>
is not Copy
?
I'm writing code that uses a lot of shared pointers, and having to type .clone()
all the time is getting on my nerves.
It seems to me that Rc<>
should just consist of a pointer, which is a fixed size, so the type itself should be Sized
and hence Copy
, right?
Am I missing something?
'Rc' stands for 'Reference Counted'. See the module-level documentation for more details. The inherent methods of Rc are all associated functions, which means that you have to call them as e.g., Rc::get_mut(&mut value) instead of value. get_mut() . This avoids conflicts with methods of the inner type T .
Rc uses non-atomic reference counting. This means that overhead is very low, but an Rc cannot be sent between threads, and consequently Rc does not implement Send . As a result, the Rust compiler will check at compile time that you are not sending Rc s between threads.
A pointer is a general concept for a variable that contains an address in memory. This address refers to, or “points at,” some other data. The most common kind of pointer in Rust is a reference, which you learned about in Chapter 4.
It seems to me that
Rc<>
should just consist of a pointer, which is a fixed size, so the type itself should beSized
and henceCopy
, right?
This is not quite true. Rc
is short for Reference Counted. This means that the type keeps track of how many references point to the owned data. That way we can have multiple owners at the same time and safely free the data, once the reference count reaches 0.
But how do we keep the reference counter valid and up to date? Exactly, we have to do something whenever a new reference/owner is created and whenever a reference/owner is deleted. Specifically, we have to increase the counter in the former case and decrease it in the latter.
The counter is decreased by implementing Drop
, the Rust equivalent of a destructor. This drop()
function is executed whenever a variable goes out of scope – perfect for our goal.
But when do we do the increment? You guessed it: in clone()
. The Copy
trait, by definition, says that a type can be duplicated just by copying bits:
Types that can be copied by simply copying bits (i.e.
memcpy
).
This is not true in our case, because: yes, we "just copy bits", but we also do additional work! We do need to increment our reference counter!
Drop
impl of Rc
Clone
impl of Rc
A type cannot implement Copy
if it implements Drop
(source). Since Rc
does implement it to decrement its reference count, it is not possible.
In addition, Rc
is not just a pointer. It consists of a Shared
:
pub struct Rc<T: ?Sized> {
ptr: Shared<RcBox<T>>,
}
Which, in turn, is not only a pointer:
pub struct Shared<T: ?Sized> {
pointer: NonZero<*const T>,
_marker: PhantomData<T>,
}
PhantomData
is needed to express the ownership of T
:
this marker has no consequences for variance, but is necessary for dropck to understand that we logically own a
T
.For details, see: https://github.com/rust-lang/rfcs/blob/master/text/0769-sound-generic-drop.md#phantom-data
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With