Is there a better way to collect trait objects?
⚓ Rust 📅 2025-12-04 👤 surdeus 👁️ 2I have a pattern in my project where I collect some objects, use them as their concrete types a bit, and then want to gather them into a collection of trait objects and pass them somewhere else.
However, I haven't been able to write this in a way where the syntax doesn't feel obtuse to me. Is there is a trick to make type inference do what I want here?
This is a reduced example:
use std::sync::Arc;
trait MyTrait {}
struct Client1 {}
struct Client2 {}
struct Client3 {}
impl MyTrait for Client1 {}
impl MyTrait for Client2 {}
impl MyTrait for Client3 {}
struct Config {}
impl Config {
pub async fn make_client1(&self) -> Option<Client1> {
Some(Client1 {})
}
pub async fn make_client2(&self) -> Option<Client2> {
Some(Client2 {})
}
pub async fn make_client3(&self) -> Option<Client3> {
Some(Client3 {})
}
}
#[tokio::main]
async fn main() {
let config = Config {};
// Initialize (maybe) three clients in parallel, depending on if they are configured
let (client1, client2, client3) = tokio::join!(
config.make_client1(),
config.make_client2(),
config.make_client3()
);
// Maybe do stuff with client1, client2, client3 here depending on config etc.
//
// ...
// Now gather them and pass them to another object that only uses them via the trait
let mut clients: Vec<Arc<dyn MyTrait + Send + Sync>> = vec![];
// It seems to me that this should work, but type inference doesn't work out and I get type errors
//clients.extend(client1.into_iter().map(Arc::new));
//clients.extend(client2.into_iter().map(Arc::new));
//clients.extend(client3.into_iter().map(Arc::new));
// error[E0271]: expected `new` to return `Arc<dyn MyTrait + Send + Sync>`, but it returns `Arc<Client1>`
// --> src/main.rs:41:20
// |
//41 | clients.extend(client1.into_iter().map(Arc::new));
// | ------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Arc<dyn MyTrait + Send + Sync>`, found `Arc<Client1>`
// | |
// | required by a bound introduced by this call
// |
// = note: expected struct `Arc<dyn MyTrait + Send + Sync>`
// found struct `Arc<Client1>`
// = help: `Client1` implements `MyTrait` so you could box the found value and coerce it to the trait object `Box<dyn MyTrait>`, you will have to change the expected type as well
// If I try to guide the compiler to use the right kind of Arc::new, it doesn't work:
//clients.extend(client1.into_iter().map(Arc::<dyn MyTrait + Send + Sync>::new));
//clients.extend(client2.into_iter().map(Arc::<dyn MyTrait + Send + Sync>::new));
//clients.extend(client3.into_iter().map(Arc::<dyn MyTrait + Send + Sync>::new));
// --> src/main.rs:57:78
// |
// 3 | trait MyTrait {}
// | ------------- doesn't satisfy `dyn MyTrait + Send + Sync: Sized`
//...
//57 | clients.extend(client1.into_iter().map(Arc::<dyn MyTrait + Send + Sync>::new));
// | ^^^ function or associated item cannot be called on `Arc<dyn MyTrait + Send + Sync>` due to unsatisfied trait bounds
// |
// = note: the following trait bounds were not satisfied:
// `dyn MyTrait + Send + Sync: Sized`
// This works and is fairly succinct, but it feels very obtuse.
// A lot of developers won't quickly realize what the `as _` is doing or why it has to be there.
// Is there a cleaner way to do this?
clients.extend(client1.into_iter().map(|x| Arc::new(x) as _));
clients.extend(client2.into_iter().map(|x| Arc::new(x) as _));
clients.extend(client3.into_iter().map(|x| Arc::new(x) as _));
}
Thanks
2 posts - 2 participants
🏷️ Rust_feed