Object Identity


#1

Hello,

I wonder how to test if two references point to the same object. Googling brought up a lot of questions on this matter, but no satisfactory answer to this.

In my particular case, I have a vector of closures and want to remove a certain entry from the vector. I can work around this by wrapping the closures in a struct that has an id, but I don’t think that this is the right way of doing this.

Thanks
Martin


#2

You can test the pointer value, which is safe as soon as you don’t deference it.

Simple example with i32
In the first case b is a copy of a, so the value is the same, but their position in memory is different.
In the second case ref_a is a reference, and ref_b is a copy of the reference. Now the referenced address is the same and can be checked as well.

Sidenote: if you want to remove data from a vector, it must be mut, so you cannot have a non-mutable reference to it, which means you can’t safely store reference to the inner elements. So, I fear that you need to go unsafe for your purpose, but I am not completely sure.


#3

Thanks for your help, @dodomorandi. I came across similar solutions, but it seemed to me that testing the pointer value has some caveats / limitations (E.g. comparing references to trait objects). It looks a bit odd to me that rust has no operator for this.


#4

Well, Rust doesn’t have objects, so naturally there’s no operation for checking their identity. Should two pointers that point to the interior of the same “object” be equal? What if you have pointers that point to different types, but have the same address; should they be equal?


#5

There is Rc#ptr_eq and Arc#ptr_eq if you are using those.


#6

There’s core::ptr::eq https://doc.rust-lang.org/core/ptr/fn.eq.html to avoid manual casts.


#7

Nice!
Good or bad, in my little experience with Rust I never needed to work directly with pointers, except for FFI.

With my daily C++ I have enough of pointers :sweat_smile:


#8

I’ve been doing as *T for years to compare for identity, until I was pointed at ptr::eq fairly recently :slight_smile: https://github.com/rust-lang/cargo/pull/5287#discussion_r178938481


#9

Just use as *const () on them and test equality. This removes any fat from trait.

As @belowm hints at Rust makes this even more difficult. It does not guarantee that there will be a single vtable per trait.


#10

ptr::eq can be occasionally useful as a micro optimization to avoid additional memory loads (ie cache misses) :slight_smile:. So instead of loading some data (eg id) from two references, you just compare the references (ptrs).


#11

I’ve created the following snippet that tried to identify some “gotchas” when dealing with trait references and their cast to pointers:
playing with pointers to trait references

// we have only one string object;
let x = String::new ();
let xp = &x as *const _ as *const ();

// we create two "trait references" poiting to it;
let a : &dyn fmt::Display = &x;
let b : &dyn fmt::Display = &x;

// the size of a "regular reference" is 64 bits (as is for pointers)
assert_eq! (mem::size_of::<& String> (), mem::size_of::<u64> ());
assert_eq! (mem::size_of::<* const()> (), mem::size_of::<u64> ());

// the size of a "trait reference" is 128 bits
// * 64 bits for the actual reference
// * 64 bits for the "internals" (whatever the compiler needs to do)
assert_eq! (mem::size_of::<&dyn fmt::Display> (), mem::size_of::<u128> ());

// we make sure that we indeed have two distinct references
// (the compiler might be "smart" and just consider them as one)
assert_ne! (&a as *const _ as *const (), &b as *const _ as *const ());

// we cast the two references "raw pointers" (similar to C `*void`)
let ap = a as *const _ as *const ();
let bp = b as *const _ as *const ();

// we check that the two obtained pointers are indeed the same
assert_eq! (ap, bp);
assert_eq! (xp, ap);
assert_eq! (xp, bp);

// we try to "destruct" the "trait reference"
let (ar, ai) : (u64, u64) = unsafe { mem::transmute (a) };
let (br, bi) : (u64, u64) = unsafe { mem::transmute (b) };

// and it seems that the first 64 bits is the actual pointer
assert_eq! (xp as u64, ar);
assert_eq! (xp as u64, br);

// and it also seems that the "internals" pointer is the same
assert_eq! (ai, bi);

In conclusion it seems that even when using &dyn Trait references if one casts it as <reference> as *const _ as *const() you obtain the correct result. (The only thing you have to watch for is that <reference> is an actual reference to the trait, and not a reference to the reference to the trait (i.e. &&dyn Trait).