rust

Compare underlying objects in Arc<Mutex<dyn Trait>> without double-locking deadlock?


I have two traits IFoo and IBar, with concrete implementations Foo and Bar.

The goal: store them as Arc<dyn Any + Send + Sync> and later compare (repeatedly using different Any wrappers) the underlying objects by memory address.

Question:

How can I compare whether two Arc<Mutex<dyn IBar>> point to the same underlying object without running into a deadlock from double-locking?

Here’s a simplified version of the code:

use std::any::Any;
use std::sync::{Arc, Mutex};

// types   

pub trait IFoo: Any + Send + Sync {}
pub struct Foo {}
impl dyn IFoo {
    pub fn as_any(&self) -> &dyn Any {
        self
    }
}
impl IFoo for Foo {}

pub trait IBar: Any + Send + Sync {}
pub struct Bar {}
impl dyn IBar {
    pub fn as_any(&self) -> &dyn Any {
        self
    }
}
impl IBar for Bar {}

// Library

pub struct Comparator {
    left: Arc<dyn Any + Send + Sync>,
}

impl Comparator {
    pub fn compare(&self, right: &Arc<dyn Any + Send + Sync>) -> bool {
        // Non-Mutex comparison works fine
        if let (Ok(a_arc), Ok(b_arc)) = (
            self.left.clone().downcast::<Arc<dyn IFoo>>(),
            right.clone().downcast::<Arc<dyn IFoo>>(),
        ) {
            // Downcast trait object to concrete Foo
            let a_concrete = a_arc.as_ref().as_any().downcast_ref::<Foo>();
            let b_concrete = b_arc.as_ref().as_any().downcast_ref::<Foo>();
            if let (Some(a_ctrl), Some(b_ctrl)) = (a_concrete, b_concrete) {
                // Compare actual memory addresses of the underlying Foo
                return (a_ctrl as *const Foo) == (b_ctrl as *const Foo);
            }
        }

        // With Mutex — this deadlocks if left and right point to the same underlying Bar
        // Downcast trait object to concrete Bar
        if let (Ok(a_arc), Ok(b_arc)) = (
            self.left.clone().downcast::<Arc<Mutex<dyn IBar>>>(),
            right.clone().downcast::<Arc<Mutex<dyn IBar>>>(),
        ) {
            let a_guard = a_arc.lock().unwrap(); // ⚠️ locks once (naive implementation)
            let b_guard = b_arc.lock().unwrap(); // ⚠️ locks again on the same object → deadlock

            let a_concrete = a_guard.as_any().downcast_ref::<Bar>();
            let b_concrete = b_guard.as_any().downcast_ref::<Bar>();

            if let (Some(a_med), Some(b_med)) = (a_concrete, b_concrete) {
                return (a_med as *const Bar) == (b_med as *const Bar);
            }
        }

        panic!("Unsupported type");
    }

}

Test

#[test]
fn main() {
    // Consumer of the Library
    // Works: Foo without Mutex
    let foo: Arc<dyn IFoo> = Arc::new(Foo{});
    let foo_any: Arc<dyn Any + Send + Sync> = Arc::new(foo.clone()); // wrapper Any because Comparator only accepts Any

    let sut = Comparator { left: foo_any.clone() };
    assert!(sut.compare(&foo_any)); // true

    let foo_any2: Arc<dyn Any + Send + Sync> = Arc::new(foo.clone()); // another Any wrapper
    assert!(sut.compare(&foo_any2)); // (repeat test later) also true (same underlying object)

    // Fails: Bar with Mutex
    let bar: Arc<Mutex<dyn IBar>> = Arc::new(Mutex::new(Bar{}));
    let bar_any: Arc<dyn Any + Send + Sync> = Arc::new(bar.clone());

    let sut = Comparator {left: bar_any.clone()};

    // let result = sut.compare(&bar_any); // deadlocks here
}

Lateral Thinking:

Is there a clean way to do this in Rust? I'm open to any suggestions or a totally different approach.


Solution

  • A possible idea is to lock the first, get a raw pointer to it, release the lock, then lock the second:

    if let (Ok(a_arc), Ok(b_arc)) = (
        self.left.clone().downcast::<Arc<Mutex<dyn IBar>>>(),
        right.clone().downcast::<Arc<Mutex<dyn IBar>>>(),
    ) {
        let a_ptr = {
            let a_guard = a_arc.lock().unwrap();
            let a_concrete = a_guard.as_any().downcast_ref::<Bar>();
            a_concrete.map(|it| std::ptr::from_ref(it))
        };
        let b_ptr = {
            let b_guard = b_arc.lock().unwrap();
            let b_concrete = b_guard.as_any().downcast_ref::<Bar>();
            b_concrete.map(|it| std::ptr::from_ref(it))
        };
        
        if let (Some(a_med), Some(b_med)) = (a_ptr, b_ptr) {
            return a_med == b_med;
        }
    }