Rc ptr comparison: ptr_eq vs Rc.as_ptr() as usize

Suppose we have:

a: Rc<dyn AnimalT>;
b: Rc<dyn AnimalT>;

Now, suppose we want to do a comparison. We know the problems of ptr_eq in Rc<dyn Trait> ptr equality

Question: is there any downside to use:

a.as_ptr() as usize == b.as_ptr() as usize

(and I am also surprised that ptr_eq is not implemented this way)

EDIT: as_raw should be as_ptr, (thanks @L.F )

I think that should be fine.

I don't see a method named as_raw in std. Are you referring to std::rc::Rc::as_ptr?

@L.F: Thanks! Fixed.

@alice : Any intuition on why this is not the default behaviour of ptr_eq ?

Rc::ptr_eq is implemented like this:

pub fn ptr_eq(this: &Self, other: &Self) -> bool {
    this.ptr.as_ptr() == other.ptr.as_ptr()
}

So you're basically asking why pointer comparison compares the vtable section. I'm guessing it's because you can easily convert fat pointers to the address, so the general comparison is offered by default, but I'm not sure.

Here's an example from the documentation of std::ptr::eq:

Traits are also compared by their implementation:

#[repr(transparent)]
struct Wrapper { member: i32 }

trait Trait {}
impl Trait for Wrapper {}
impl Trait for i32 {}

let wrapper = Wrapper { member: 10 };

// Pointers have equal addresses.
assert!(std::ptr::eq(
    &wrapper as *const Wrapper as *const u8,
    &wrapper.member as *const i32 as *const u8
));

// Objects have equal addresses, but `Trait` has different implementations.
assert!(!std::ptr::eq(
    &wrapper as &dyn Trait,
    &wrapper.member as &dyn Trait,
));
assert!(!std::ptr::eq(
    &wrapper as &dyn Trait as *const dyn Trait,
    &wrapper.member as &dyn Trait as *const dyn Trait,
));

// Converting the reference to a `*const u8` compares by address.
assert!(std::ptr::eq(
    &wrapper as &dyn Trait as *const dyn Trait as *const u8,
    &wrapper.member as &dyn Trait as *const dyn Trait as *const u8,
));
1 Like

Trait objects are not the only kind of unsized type; slices are the other.

Using slices it's trivial to get two fat pointers that have the same address, but different metadata. And comparing the metadata does make sense for slices, because it's just the length. Two pointers that point to different sections of the same array, even if they start at the same location, can be said to point to different things.

So that's one justification for defining ptr::eq that way. That doesn't apply to Rc, because two Rc<[T]>s don't overlap unless they overlap entirely. And you could say the same for trait objects... unless you're using a transparent newtype with a different trait implementation, I guess? It's arguable. So I think you can make an argument that Rc::ptr_eq should just ignore fat pointer metadata.

But it doesn't work that way today. Can it be fixed? Well, to change the behavior of ptr_eq, you'd need to argue that the previous behavior was a bug, and any code that relies on it is incorrect. I think this is most likely true, actually, but the change would also break the symmetry between Rc::ptr_eq and ptr::eq, so there's an argument to be had there.

The other option would be to introduce a new method Rc::thin_ptr_eq and deprecate ptr_eq in favor of it.

(Incidentally: One of my annoyances with Rc is that you can't ptr_eq an Rc with a Weak unless you first upgrade the Weak. If we're talking about possible improvements to Rc::ptr_eq, maybe this is something that could be taken into consideration.)

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.