Box identity for object equivalence


#1

Hi all. I’m writing a path tracer in Rust and I’m adding something that requires me iterating over a bunch of Boxed Renderables, seeing which is “nearest”. I then want to see if the nearest Boxed Renderable is one particular one (in this case, a light source). If it is, we’re done: I can find a path to the light (and there’s no shadow).

In C/C++ I’d use the pointer value itself as the identity for comparison, rather than having to write operator==. Is there an equivalent in Rust?

The code looks like this:

fn shadow_cast(&self, ray: &Ray, light: &Box<Renderable>) -> bool {
    let mut hit_obj : Option<&Box<Renderable>> = None;
    let mut hit_dist = f64::INFINITY;
    for obj in self.objects.iter() {
        if let Some(dist) = obj.intersect(&ray) {
            if dist < hit_dist {
                hit_dist = dist;
                hit_obj = Some(&obj);
            }
        }
    }
    match hit_obj {
        None => { false },
        Some(obj) => {
            if obj == light { true } else { false } // can't do this :)
        }
    }
}

Thanks in advance!

–matt


#2

With thanks to the awesome folk on the Rust IRC channel, I got the following working:

fn shadow_cast(&self, ray: &Ray, light: &Renderable) -> bool {
    let mut hit_obj : Option<&Renderable> = None;
    let mut hit_dist = f64::INFINITY;
    for obj in self.objects.iter() {
        if let Some(dist) = obj.intersect(&ray) {
            if dist < hit_dist {
                hit_dist = dist;
                hit_obj = Some(&**obj);
            }
        }
    }
    match hit_obj {
        None => { false },
        Some(obj) => {
            if obj as *const Renderable == light as *const Renderable { true } else { false }
        }
    }
}

That is, using a pointer cast to get my “C-like” identity.


#3

Is it really necessary to use raw pointers here? That seems kind of unfortunate…


#4

It’s perfectly safe (hence no unsafe block). You’re basically asking “do these two objects live at the same memory address?”. If you don’t like looking at raw pointers, write a helper:

fn are_same<T>(a: &T, b: &T) -> bool { a as *const T == b as *const T }

#5

Makes sense, thanks.


#6

BTW, should this section be changed then? It talks about “unsafe pointers” all the time instead of “raw pointers”…

http://static.rust-lang.org/doc/master/std/primitive.pointer.html


#7

Almost everything you might want to do with a raw pointer will be unsafe. This is pretty much the only exception.

/cc @steveklabnik?


#8

The thing needs a name. Either it’s “raw pointer”, or it’s “unsafe pointer”. Using both interchangeably leads to confusion (and possibly FUD like my earlier post).


#9

IMO, the terminology should be that the pointer itself is raw, and dereferencing it is unsafe.


#10

Yeah I was just going to say, the only thing unsafe about raw pointers is dereferencing them, although thats almost everything you do with raw pointers :stuck_out_tongue:


#11

Yes, it should. The name is ‘raw pointer’


#12

All this is great but sadly I hit an ICE actually using it, and apparently there’s another term : “fat pointers”. I closed my issue as a duplicate of this issue which is easier to read, but now I’m wondering: if “fat pointers” mean pointers to the trait object, is it possible the pointer comparison isn’t even valid? That is; if a new trait object has been created in order to allow the cast, then of course the pointers will differ, even for the same “underlying” object.

That is, something like: http://is.gd/KN0VTl - if a different trait object is created for “a” and “b” parameters, then even if they point at the same object the comparison will fail.

So, perhaps an “identity” method trait would be needed? Maybe one that returns a u64 “opaque” identity (i.e. the address of the underlying struct data).


#13

My current workaround is to add an “identity” method to my trait: fn identity(&self) -> i64;

Then in each implementation I use the same boilerplate:

fn identity(&self) -> u64 { self as *const Self as u64 }

this prevents creating a “fat pointer” as the underlying struct address is used instead.