Why can comparing two seemingly equal pointers with == return false?

后端 未结 1 741
猫巷女王i
猫巷女王i 2020-12-06 15:55

I want to test if two objects of type Rc contain the same instance of a concrete type, so I compare pointers to the objects inside Rc

相关标签:
1条回答
  • 2020-12-06 16:47

    When is a "pointer" not a "pointer"? When it's a fat pointer. ObjectInterface is a trait, which means that &dyn ObjectInterface is a trait object. Trait objects are composed of two machine pointers: one for the concrete data and one for the vtable, a set of the specific implementations of the trait for the concrete value. This double pointer is called a fat pointer.

    Using a nightly compiler and std::raw::TraitObject, you can see the differences:

    #![feature(raw)]
    
    use std::{mem, raw};
    
    pub fn is_same(left: &Object, right: &Object) -> bool {
        let a = left.as_ref() as *const _;
        let b = right.as_ref() as *const _;
        let r = a == b;
        println!("comparing: {:p} == {:p} -> {}", a, b, r);
    
        let raw_object_a: raw::TraitObject = unsafe { mem::transmute(left.as_ref()) };
        let raw_object_b: raw::TraitObject = unsafe { mem::transmute(right.as_ref()) };
        println!(
            "really comparing: ({:p}, {:p}) == ({:p}, {:p})",
            raw_object_a.data, raw_object_a.vtable,
            raw_object_b.data, raw_object_b.vtable,
        );
    
        r
    }
    
    comparing: 0x101c0e010 == 0x101c0e010 -> true
    really comparing: (0x101c0e010, 0x1016753e8) == (0x101c0e010, 0x1016753e8)
    comparing: 0x101c0e010 == 0x101c0e010 -> false
    really comparing: (0x101c0e010, 0x101676758) == (0x101c0e010, 0x1016753e8)
    

    It turns out that (at least in Rust 1.22.1) each code generation unit creates a separate vtable! This explains why it works when it's all in the same module. There's active discussion on if this is a bug or not.

    When you annotate the new and run functions with #[inline] the consumers will use that vtable.


    As Francis Gagné said:

    You can change as *const _ to as *const _ as *const () to turn the fat pointer into a regular pointer if you only care about the value's address.

    This can be cleanly expressed using std::ptr::eq:

    use std::ptr;
    
    pub fn is_same(left: &Object, right: &Object) -> bool {
        let r = ptr::eq(left.as_ref(), right.as_ref());
        println!("comparing: {:p} == {:p} -> {}", left, right, r);
        r
    }
    
    0 讨论(0)
提交回复
热议问题