How to convert Arc<Mutex<Box<Struct>>>
to Arc<Mutex<Box<dyn Trait>>>
when a function requires Arc<Mutex<Box<dyn Trait>>>
as parameter?
In the following code snippet, the age_plus
function requires a type of Arc<Mutex<Box<dyn Animal>>>
, but only a type of Arc<Mutex<Box<Dog>>>
is available.
use std::sync::{Arc, Mutex};
pub trait Animal {
fn age_plus(&mut self) -> u32;
}
pub struct Dog {
age: u32,
pub name: String,
}
impl Animal for Dog {
fn age_plus(&mut self) -> u32 {
self.age += 1;
self.age
}
}
impl Dog {
pub fn print_name(&self) {
println!("name: {}", self.name);
}
}
pub fn age_plus(animal: Arc<Mutex<Box<dyn Animal>>>) {
animal.lock().unwrap().age_plus();
}
fn main() {
let dog = Arc::new(Mutex::new(Box::new(Dog {
age: 10,
name: "dog".to_string(),
})));
dog.lock().unwrap().print_name();
println!("dog age: {}", dog.lock().unwrap().age_plus());
// how to convert Arc<Mutex<Box<Dog>>> to Arc<Mutex<Box<dyn Animal>>>?
age_plus(dog.clone());
}
This cannot be done.
The reason is that Box<Dog>
occupies the size of pointer bytes in memory, but Box<dyn Animal>
occupies double times the size of a pointer in memory, since it also needs to store the vtable. So Arc<Mutex<Box<Dog>>>
needs to grow to contain Arc<Mutex<Box<dyn Animal>>>
, and that is impossible.
If you cannot change the function in any way, your only chance is cloning the type and creating a new type:
let dyn_animal = Arc::new(Mutex::new(Box::new(Dog::clone(&dog.lock().unwrap())) as _));
age_plus(dyn_animal);
Of course, Dog
must be Clone
. You might be able to avoid the clone by storing Box<dyn Animal>
from the beginning, but it won’t work if you need to call Dog
-specific methods.
Another possible alternative, if you don’t need the Arc<Mutex>
at the beginning and don’t need to call Dog
-specific methods afterwards, is to only construct Box<dyn Dog>
. This is easily convertible to Box<dyn Animal>
, and it can be wrapped later with Arc<Mutex>
:
fn main() {
let mut dog = Box::new(Dog {
age: 10,
name: "dog".to_string(),
});
dog.print_name();
println!("dog age: {}", dog.age_plus());
let dog = Arc::new(Mutex::new(dog as Box<dyn Animal>));
age_plus(Arc::clone(&dog));
}
If you can change the function, you have several options:
The best way, if possible, is to take &mut dyn Animal
as @AleksanderKrauze said. This way you can provide all sorts of animals:
pub fn age_plus(animal: &mut dyn Animal) {
animal.age_plus();
}
fn main() {
let dog = Arc::new(Mutex::new(Box::new(Dog {
age: 10,
name: "dog".to_string(),
})));
dog.lock().unwrap().print_name();
println!("dog age: {}", dog.lock().unwrap().age_plus());
age_plus(&mut **dog.lock().unwrap());
}
If this is not possible, consider taking Arc<Mutex<dyn Animal>>
. Arc<Mutex<Dog>>
can be converted to that:
pub fn age_plus(animal: Arc<Mutex<dyn Animal>>) {
animal.lock().unwrap().age_plus();
}
fn main() {
let dog = Arc::new(Mutex::new(Dog {
age: 10,
name: "dog".to_string(),
}));
dog.lock().unwrap().print_name();
println!("dog age: {}", dog.lock().unwrap().age_plus());
age_plus(Arc::clone(&dog) as _);
}
You can’t directly convert these types without stealing/copying the
Dog
. However, you could instead implementAnimal
onArc<Mutex<Box<T>>> where T: Animal
. play.rust-lang.org/…Function requiring
Arc<Mutex<Box<dyn Trait>>>
as its input, is a giant code smell, and will be source of nothing than problems. Consider accepting&mut dyn Trait
instead and locking the mutex before you call this function.Thank you for your answer. If cannot modify the input parameters of age_plus, play.rust-lang.org/…
@AleksanderKrauze “Giant code smell” could be perceived as somewhat dismissive. The function might have valid reasons to accept the
Arc
, such as needing to store it somewhere. The OP has obviously simplified the use case for the sake of providing a MRE, not to mention that they might not control the definition ofage_plus()
.