I think Rc
does in practice because in the implementation of Rc::new
, we find:
box RcBox { strong: Cell::new(1), weak: Cell::new(1), value }
The RcBox
isn't a ZST, even if value
is. However, I'm still not sure if there are guarantees in the reference that this really results in a unique pointer also in future, depending on how the value is used and which future optimizations might be added to the compiler. (That's why I'm keep coming back to ask questions about guarantees in the reference or other normative documentation all time.)
Box::new
doesn't allocate if the argument is a ZST (see documentation of Box::new
). This isn't explicitly stated in the documentation of Box::pin
, but also applies under the current implementation. Thus Box::pin
does not generate a unique address in all cases. Consider:
let b1 = Box::pin(());
let b2 = Box::pin(());
assert_ne!(
&*b1 as *const _,
&*b2 as *const _
); // fails
(Playground)
But compare with Rc
:
let rc1 = Rc::new(());
let rc2 = Rc::new(());
assert_ne!(
&*rc1 as *const _,
&*rc2 as *const _
); // passes
}
(Playground)
I noticed that RefId
fails my intended definition of identity checks, because RefId(rc1) == RefId(rc2)
while rc1
and rc2
are certainly not interchangable.
Apparently, my "hack" to treat zero-sized pointees differently causes undesired behavior (according to my own definition) in case of Rc
s (or Arc
s) because two distinct Rc<()>
values will be consiered equal even if they work with different counters.
What to learn from all of this or what to do about it? I'm not sure. Let's keep in mind that pointer comparisons for "identity" or "cheap equality" checks are done in real-life Rust (including the Rust compiler itself). (Yet another use-case seems to have been discussed in this thread, but please correct me if I'm wrong here and that is a different case.) To back up this statement, also consider that there exists a method Rc::ptr_eq
in the standard library, even though the standard library doesn't seem to provide a method to store Rc
's in a hash map using that method-implemented equality relation (ptr_eq
).
The reason why Rc<()>
behaves different than Box<()>
(or Pin<Box<()>>
) is because of two things:
Rc::as_ptr
(which is used by Rc::ptr_eq
) will calculate the pointer using ptr::addr_of_mut!
on a non-zero-sized struct (RcBox
) that is defined as #[repr(C)]
in the source of Rc
.
- Boxes seem to always allocate if the inner type is non-zero-sized. (Which might or might not be guaranteed by any normative documentation? I'd really like to know.) And they never allocate if the inner type is zero-sized.
Note that ByAddress
uses Deref
, which in turn will also use as_ptr
if I understand the source correctly. Thus ByAddress
can be used to compare the identity of two Rc
s or Arc
s (while RefId
can't if the inner type T
is zero-sized).
I wonder if it would make sense to introduce a new trait like Identity
or something like that, which could be implemented for Rc
and Arc
, and maybe for a couple of other types as well.
(Edit: Sorry, I made a copy&paste mistake on my laptop and removed the duplicated parts. Currently on the road here.)
(Edit #2: Added: "And they never allocate if the inner type is zero-sized.")