Why does this reference equality-checking code behave differently depending on context?


#1

I’m trying to implement a text user interface library for Rust and am currently running into a weird situation while checking whether two references to a widget are the same. I’ve created a minimal example and uploaded it to this GitHub repository.

The basic design idea of the library: Widgets implement the Widget trait and will be passed around by the TUI library as trait objects. This way, users can implement their own widgets by implementing the trait for their own structs.

In order to build up a hierarchy of Widgets, it is possible to define strong references to Widgets using Rc-based WidgetRefs and Weak references for e.g. passing the currently focused widget as a function argument. WidgetRef and WidgetWeakRef are defined like so:

pub struct WidgetRef (Rc<RefCell<Box<Widget>>>);
pub struct WidgetWeakRef (Weak<RefCell<Box<Widget>>>);

When rendering a widget, I want to pass a focus: &WidgetWeakRef to the currently focused widget to the render method of every widget so the widget can decide if it is currently in focus by checking if self == focus. To do this, I’ve tried to implement a PartialEq for WidgetRefs like so:

impl<W: Widget> PartialEq<W> for WidgetRef {
    fn eq(&self, other: &W) -> bool {

        use std::ops::Deref;
        use std::borrow::Borrow;

        let rc_ref: &RefCell<Box<Widget>> = self.0.deref();
        let ref_ref: Ref<Box<Widget>> = rc_ref.borrow();
        let self_ref: &Widget = ref_ref.deref().deref();

        self_ref as *const Widget == other as *const Widget;
    }
}

This check seems to succeed in some situations and fail in others - to illustrate this, I define two example Button widgets - ButtonA using a generic type T: Display+Debug to denote the caption text and ButtonB simply using a String:

pub struct ButtonA<T: Display+Debug> {
    pub text: T,
}

impl<T: Display+Debug> Widget for ButtonA<T> {
    fn render(&self, focus: &WidgetWeakRef) {
        println!("{} (focus={})", self.text, focus == self);
    }
}

pub struct ButtonB {
    pub text: String,
}

impl Widget for ButtonB {
    fn render(&self, focus: &WidgetWeakRef) {
        println!("{} (focus={})", self.text, focus == self);
    }
}

I test the described library using the following code:

pub fn main() {
    println!("I am main() in src/lib.rs");

    let btn_a: WidgetRef = Box::new(ButtonA { text: String::from("ButtonA: using generics")} ).into();
    let btn_a_ref = btn_a.weak_ref();

    let btn_b: WidgetRef = Box::new(ButtonB { text: String::from("ButtonB: simple")} ).into();
    let btn_b_ref = btn_b.weak_ref();

    btn_a.render(&btn_a_ref);
    btn_b.render(&btn_a_ref);

    println!("Switching focus");

    btn_a.render(&btn_b_ref);
    btn_b.render(&btn_b_ref);

}

Now for the weird part - the test code behaves differently for ButtonA and ButtonB and depending on whether fn main is in the same module as the rest of the code:

If fn main() is in the same module everything works like it should:

$ cargo run --bin same
   Compiling tui v0.1.0 (file:///home/seemayer/Projects/Rust/tui2)
     Running `target/debug/same`
I am main() in src/lib.rs
ButtonA: using generics (focus=true)
ButtonB: simple (focus=false)
Switching focus
ButtonA: using generics (focus=false)
ButtonB: simple (focus=true)

But if I have a separate module for the main function, the reference equality check for ButtonB fails while the reference equality check for the generics-using ButtonA still succeeds:

   Compiling tui v0.1.0 (file:///home/seemayer/Projects/Rust/tui2)
     Running `target/debug/separate`
I am main() in src/separate.rs
ButtonA: using generics (focus=true)
ButtonB: simple (focus=false)
Switching focus
ButtonA: using generics (focus=false)
ButtonB: simple (focus=false)

I’ve tested this on both stable 1.8.0 and nightly e0fd34bba (2016-05-09) and am getting the same behavior. My theory would be that this is somehow related to the internal representation of generic vs. non-generic types, combined with some type inference subtleties but have run out of ideas on how to debug this any further.

Can anyone explain to me why in this example:

  1. it matters whether the main function is in the same module and
  2. it matters whether the struct has a generic type or not

Thanks in advance for any help!


#2

Equality comparisons on pointers to traits are generally problematic. The comparison compares both the data pointer and the vtable pointer. This leads to problems because vtable pointers aren’t globally unique: there can be multiple copies of the same vtable in a program, and copies of different vtables can be merged. (The end result depends on how you split your code into different crates and your optimization settings.)

For your example, you might be able to use something like self_ref as *const Widget as *const () == other as *const Widget as *const ().


#3

Thank you so much, this works :+1:! I’ll be sure to credit you in my library once I’m ready to release!