Is there a better way to collect trait objects?

⚓ Rust    📅 2025-12-04    👤 surdeus    👁️ 2      

surdeus

I 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

Read full topic

🏷️ Rust_feed