Issue finding the position inside a collection of borrowed trait object

Hello,
I face on a current problem about comparing borrowed references combined with traits. Indeed, I want to have a collection of borrowed object using the trait object. Then, later, I want to found the index of the borrowed object inside the collection.

To be more precise, I have written a small code below that describe the current issue:

// Mesh with Emitter trait
pub trait Emitter {
}
pub struct Mesh {
}
impl Emitter for Mesh {
}

fn main() {
    // Some object allocated
    let other_mesh = Mesh {}; 
    let objects = vec![Mesh {}, Mesh {}];
    // I want to store some of the object as borrow (but with trait type)
    let mut traits: Vec<&dyn Emitter> = vec![];
    for m in &objects {
        traits.push(m);
    }
    
    // Show the ptr collections
    // I guess the first print is not working as it is not borrowed, right?
    println!("Objects ptr address: ");
    objects.iter().for_each(|m| println!(" - {:p}", m));
    println!("Traits ptr address: ");
    traits.iter().for_each(|m| println!(" - {:p}", m));
    
    // One way to found what is the object index inside the "trait" vector
    let find = |traits: &Vec<&dyn Emitter>, k: &dyn Emitter| {
        println!("Call find with: {:p}", k);
        match traits.iter().position(|m| std::ptr::eq(k, *m)) {
            Some(ref i) => println!("Found at pos: {}", i),
            None => println!("Object {:p} not found", k),
        }
    };
    
    // Another way to find the index
    let find_explicit = |traits: &Vec<&dyn Emitter>, k: &dyn Emitter| {
        println!("Call find_explicit with: {:p}", k);
        for i in 0..traits.len() {
            if traits[i] as *const _ ==  k as *const _ {
                println!("Found at pos: {}", i);
                return;
            }
        }
        println!("Object {:p} not found", k);
    };
    
    // Search for one object
    // Excepted that the last find do not work
    find(&traits, traits[0]);
    find(&traits, traits[1]);
    find(&traits, &other_mesh);
    
    // Search explicit for one object
    find_explicit(&traits, &objects[0]);
    find_explicit(&traits, &objects[1]);
    find_explicit(&traits, &other_mesh);
}

(Playground)

Output:

Objects ptr address: 
 - 0x1
 - 0x1
Traits ptr address: 
 - 0x559f3bf72a40
 - 0x559f3bf72a50
Call find with: 0x1
Found at pos: 0
Call find with: 0x1
Found at pos: 0
Call find with: 0x7fff82159908
Object 0x7fff82159908 not found
Call find_explicit with: 0x1
Found at pos: 0
Call find_explicit with: 0x1
Found at pos: 0
Call find_explicit with: 0x7fff82159908
Object 0x7fff82159908 not found

So as you can observe, I am not able to get the current valid for a given object. I wondering if there is a proper way to do so.

Note that traits.iter() gives you &&dyn Emitter items, which is why the addresses look different. If you destructure that with |&m|, you’ll get the &dyn Emitter which should match the object addresses.

Since your Mesh is a zero-sized type, the Vec<Mesh> isn’t actually allocating any memory. So all of the items just have an aligned dangling pointer address, 0x1. There’s no way to distinguish the items in this Vec by address, nor any other way since they have no data.

1 Like

Thanks for your response! With your comment, I have easily fixed the playground rust code. Unfortunately, it turns out that this simple example does not capture well my current problem.

Indeed, in my current problem is still related to finding the index inside a collection of borrowed traits. The code is very similar to the playground code:

pub struct EmitterSampler<'scene> {
    pub emitters: Vec<&'scene dyn Emitter>,
    pub emitters_cdf: Distribution1D,
}

impl<'scene> EmitterSampler<'scene> {
    pub fn pdf(&self, emitter: &dyn Emitter) -> f32 {
        for (i, e) in self.emitters.iter().enumerate() {
            //if emitter as *const _ == (*e) as *const _ {
            if std::ptr::eq(emitter, *e) {
                // I need the index to retrive an info
                // This info cannot be stored inside the Emitter
                return self.emitters_cdf.pdf(i);
            }
        }

        // For debug
        println!("Size: {}", self.emitters.len());
        for e in &self.emitters {
            println!(" - {:p} != {:p}", (*e), emitter);
        }

        panic!("Impossible to found the emitter: {:p}", emitter);
    }

The output of the error (when I running my program):

Size: 4
 - 0x55e496fc97a0 != 0x55e496fc97a0
 - 0x55e496fc9800 != 0x55e496fc97a0
 - 0x55e496fc9860 != 0x55e496fc97a0
 - 0x55e496fc98c0 != 0x55e496fc97a0

You can observe that the address seems to match. The super weird behavior is that if I comment some code (that I do not use) which contains &dyn Emitter, the code starts to working again (no problem). If I left the “problematic code” and disable the multithreading (using rayon), the problem is also gone. Does anyone have this weird behavior before?

This is quite weird because my code does not use any unsafe code. So I wonder why I got this behavior. I will continue to investigate and see if I can isolate the issue in a toy example.

I think what’s happening is that ptr::eq is comparing the complete “fat” pointer, both the object address and the vtable, while your debug print only shows the first. You could mem::transmute to (usize, usize) to get a peek at both. The vtable could be different even with the same type if the code producing it comes from different compilation/codegen units, and seemingly innocent changes in your code can influence that codegen split in unpredictable ways.

I think the easiest way to solve your comparison is to cast as *const _ as usize, which loses the vtable information, and just compare that raw number.

2 Likes

Thanks! Your solution worked :slight_smile:

The vtable was different, but the (raw/thin) ptr was the same. By only comparing the first usize (as converting straight to usize did not work), the code produces correct results. This is not an ideal solution but I will stick with it for now. Thanks again.

PS: Note I found another person have a similar problem: “[Solved] Arc, traits, inheritance, and how to compare two pointers” It is funny that both of us had the same problem when developing a light transport engine :smiley:

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.