[Solved] Arc, traits, inheritance, and how to compare two pointers


#1

In my code I would like to check if two pointers are pointing to the same location:

...
/// Computes a direct lighting estimate for a single light source sample.
pub fn estimate_direct(...
                       light: Arc<Light + Send + Sync>,
...
                       specular: bool)
                       -> Spectrum {
...
                if let Some(light_isect) = scene.intersect(&mut ray) {
                    found_surface_interaction = true; 
                    if let Some(primitive) = light_isect.primitive {
                        if let Some(area_light) = primitive.get_area_light() {
                            // I would like to compare pointers here:
                            if Arc::ptr_eq(&area_light, &light) {

The error message I get is:

cargo test --release
   Compiling pbrt v0.1.11 (file:///home/jan/git/self_hosted/Rust/pbrt)
error[E0308]: mismatched types
    --> src/lib.rs:9274:57
     |
9274 |                             if Arc::ptr_eq(&area_light, &light) {
     |                                                         ^^^^^^ expected trait `AreaLight`, found trait `Light`
     |
     = note: expected type `&std::sync::Arc<AreaLight + std::marker::Sync + std::marker::Send>`
                found type `&std::sync::Arc<Light + std::marker::Sync + std::marker::Send + 'static>`

I’m using inheritance like this:

pub trait Light {
...
}
pub trait AreaLight: Light {
...
}
pub struct DiffuseAreaLight {
...
}
impl DiffuseAreaLight {
...
}
impl Light for DiffuseAreaLight {
...
}
impl AreaLight for DiffuseAreaLight {
...
}

Is this possible somehow? Or where is my thinking going wrong?


#2

Super traits aren’t strictly inheritance. They are more a constraint that the type implementing the trait must also implement the base trait and it then has all its functions.
You can get a reference by adding the function;

trait AreaLight: Light {
    fn as_light(&self) -> &Light; // just {self} in impl
...
}

Then you can use

::std::ptr::eq(area_light.as_light(), light.as_ref())

#3

An alternative to @jonh’s solution is to get the raw ptr out of the Arc, e.g. let p = &*area_light as *const _ as *const usize, and then compare those values.


#4

@john Thanks for your answer. I tried your solution and it did compile, but the equality test always failed, so I ended up using the solution proposed by @vitalyd. Thanks @vitalyd.


#5

I’m guessing @jonh’s solution doesn’t work because those returned references are fat pointers that contain the vtbl pointer and the data. When you use that “landing pad” approach to return an AreaLight as Light the vtbl changes to be the Light one, and the resulting fat pointer is different. Should be easy to check by looking at raw ptr value of &self before and after the landing pad.


#6

A basic example works
https://play.rust-lang.org/?gist=4b5a0ad310b8d5f5411c438e5405418f&version=stable
The reason for the cast function is to change the vtable, I don’t know if there is a guarantee that a trait (and then fat pointer) will only ever have a single vtable though.


#7

Interesting. Not sure how/why this works - need to think a bit more about it. Maybe someone else knows the answer in the meantime.

My understanding is that a given sized type will have the same vtbl for a given trait. Different types will have different vtbls for the same trait.