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.
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;
}
}