Reputation: 24473
I want to be able to compare two structs, when the structs contain a reference to a trait inside them. I only care about knowing that the two references are to the same memory location. My code compiles, runs, and shows that the two references are not equal. I expected them to be equal, so I have a bug. But my Debug impl shows that they are equal, so I'm confused.
I haven't been able to make a self-contained repo of my bug, but I can reproduce my Debug
problem:
#[derive(Debug, PartialEq)]
struct Intersection<'a> {
intersected: &'a dyn Intersectable,
}
trait Intersectable {}
impl<'a> core::fmt::Debug for dyn Intersectable + 'a {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Intersectable{{{:?}}}", core::ptr::addr_of!(self))
}
}
impl<'a> PartialEq for dyn Intersectable + 'a {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self, other)
}
}
#[derive(Debug, PartialEq)]
struct Sphere {}
impl Intersectable for Sphere {}
#[test]
fn comparison_bug() {
let sphere = Sphere{};
let other = Sphere{};
let i = Intersection{ intersected: &sphere };
assert_eq!(i.intersected, &sphere as &dyn Intersectable);
assert_eq!(i.intersected, &other as &dyn Intersectable);
}
In the test, the assertion assert_eq!(i.intersected, &other as &dyn Intersectable);
fails. This is correct -- I'm looking at two different instances of a Sphere
struct, so the definitely are not equal.
The problem is that the message printed by assert_eq!
says
assertion failed: `(left == right)`
left: `Intersectable{0x7faebfa72188}`,
right: `Intersectable{0x7faebfa72188}`
This is no good. I'm trying to print the address of each reference, so left and right should be different addresses (as the test correctly determined!). Why is my Debug
printing the same address for both?
I also tried making my impl of PartialEq
into addr_of!(self) == addr_of(other)
and it behaves exactly the same (the test fails correctly but the Debug message appears to show that they are the same address).
Upvotes: 1
Views: 123
Reputation: 59862
What you are currently doing is getting a pointer to the reference; you are getting addr_of!(self)
but self
is a reference. This would be a function local variable and therefore the address can be the same as another invocation. Instead, you want the address of what self
refers to, use *self
:
write!(f, "Intersectable{{{:?}}}", core::ptr::addr_of!(*self))
You can avoid using addr_of!
though by using the pointer format specifier, "{:p}"
, instead:
write!(f, "Intersectable{{{self:p}}}")
However, your test is flawed because Sphere
is a zero-sized type (ZST). This means it can take up no memory and therefore multiple ZSTs can occupy the same memory address. When I ran your test in --release
mode, it gave sphere
and other
the same address and therefore your test succeeds when you're expecting it not to.
If Sphere
is not actually intended to be a ZST (and just an artifact of minimization), then there's not much to worry about, but it is something to keep in mind when considering pointer equality.
As an additional note: you should avoid std::ptr::eq
for comparing dyn Intersectable
:
When comparing wide pointers, both the address and the metadata are tested for equality. However, note that comparing trait object pointers (
*const dyn Trait
) is unreliable: pointers to values of the same underlying type can compare inequal (because vtables are duplicated in multiple codegen units), and pointers to values of different underlying type can compare equal (since identical vtables can be deduplicated within a codegen unit).
Upvotes: 2