Confusion about Vec<Box<dyn Service<String>>> and Vec<Box<dyn Service<String, Response=_, Error=_, Future=_>>>

i encountered a situation where i wanted to create a vector of tower::Services, and started with

let mut services: Vec<Box<dyn Service<StateRequest>>>; // didn't compile in my code

i was planning to loop over some stuff, create the service and add it to the vector.
i left out the associated type. However, the compiler required me to put type annotation for the associated type, but since i wasn't sure of the exact types, i tried my luck with '_' and surprisingly it compiled:

let mut services: Vec<Box<dyn Service<StateRequest, Response = _, Error = _, Future = _>>>; // compiles

But i find it strange cause i (think) i really didn't provide the compiler with any more information. So i would like to understand what's the difference and why the second piece of code compiles?

[Edit] Just to make things more concrete, i added sample code & error below:

use tower::{ServiceBuilder, Service, ServiceExt, BoxError};

pub struct A;
impl A {
    // Associated types: Response & Error enough, (no Future)
    pub fn build_service() -> impl Service<String, Response=String, Error=anyhow::Error> + 'static
    {
        let mut svc = ServiceBuilder::new();
        let mut svc = svc.service_fn(
            |req: String| { Box::pin(async move { Ok(req.clone()) } ) } );
        svc
    }
}

#[tokio::main]
async fn main() {
    let mut services: Vec<Box<dyn Service<String>>> = Vec::new(); // Does not compile
    let mut services: Vec<Box<dyn Service<String, Response=_, Error=_>>> = Vec::new(); // Does not compile, needs all 3
    
    let mut services: Vec<Box<dyn Service<String, Response=_, Error=_, Future=_>>> = Vec::new(); // Compiles, but don't think i provide any more info than the above

    for _ in 1..10 {
        services.push(Box::new(A::build_service() ) );
    }
}

Error:

error[E0191]: the value of the associated types `Error` (from trait `Service`), `Future` (from trait `Service`), `Response` (from trait `Service`) must be specified
  --> src/trytowerbuild2.rs:24:35
   |
24 |     let mut services: Vec<Box<dyn Service<String>>> = Vec::new(); // Does not compile
   |                                   ^^^^^^^^^^^^^^^^ help: specify the associated types: `Service<String>, Response = Type, Error = Type, Future = Type>`

On a technical level, the compiler needs to know specific Response and Error associated types. dyn is not enough to them dynamic, so they have to be hardcoded to specific types.

Type inference does hardcode them to specific types, you just don't write the types. But there's no flexibility there.

There's no hard reason why the syntax has to be like this, but I think it's meant to remind you that these associated types are not flexible.

1 Like

Thanks, to confirm i understand what you are saying ..
The _ (underscore) is there only to remind me these associated types are not flexible? i am curious, how would that help/guide me or protect me from some potential issue?
On a different situation, providing only Response=_, Error=_ was enough - i.e. not providing the last Future=_. Is there some rule/heurisic in place i can learn about? I would prefer to understand then just guessing and trying until the compiler is happy.

There are two different features interacting here. Let's take a simpler type as an example:

let x: Vec = vec![1,2,3]; // error
let x: Vec<_> = vec![1,2,3]; // ok
let x: Vec<i32> = vec![1,2,3]; // ok, actual type

Vec has a type parameter that must be specified, so Vec<i32> is correct, and Vec is not.

There's another feature - type inference - that works inside functions and can fill in the blanks for you, so if you type Vec<_> it will figure out that means Vec<i32>, so that you don't have to type i32. But it doesn't mean that Vec is correct, or that this Vec<_> accepts strings. It's still Vec<i32>, you just didn't need to have i32 written explicitly.

So the same thing happens with your more complex type. It has these extra associated types that are not optional, not dynamic, and need to be specified. But type inference saves you some typing.

As for question why do you even need to type Error = _ instead of omitting it entirely. That's probably just an arbitrary language design decision, just like you need to type Vec<_> instead of Vec.

Thanks for the clarification - helps, but why does it sometimes need 2 and sometimes need 3 associated types specified, see code example

Like my approach so far is, try something if compiler complains just try something until it is happy... Neither very productive nor instructive

In general, you need to specify all associated types (in the future type defaults will make some of them optional), so if the trait you use has 2 associated types, you need to specify 2.

trait Foo {
   type Bar;
   type Baz;
}

needs Foo<Bar=(), Baz=()>.

Different traits may have different number of associated types.

In my case I am using only one trait - tower::Service which has 3 associated types - Response, Error & Future.
However, in the sample, there are times, it suffice to provide 2 exact type (but requires actual concrete types and _ is not enough), but further down I need to provide all 3 (but suddenly _ is good enough)

All rather confusing...

Oh, I see what you mean.

Your build_service() returns impl Service. The impl Trait syntax is not a real type. It's a feature specifically designed for hiding actual types, especially ones that can't be named (where you literally can't type a name of something, because it's a closure or async block that doesn't have a syntax for its exact type).

In this case build_service does NOT return dyn Service type. It returns ServiceFn type, and impl Service hides that type, and only promises that the unnamed ServiceFn is compatible with the Service that has these parameters. There is a confusing number of traits, aliases and levels of indirection in Tower, indeed.

Back to the Vec<_> analogy (which is a bit stretched at this point), I could do this:

fn print_mystery() -> impl Debug {
   return vec![1,2,3];
}

and return Vec<i32>, without ever mentioning i32, because I don't promise it holds integers, only that it can be debug-printed.

Wow, another different piece to understand... I think I'll need try to understand all this ... Seems rust is a very complex language.

Think your debug example provides some insight into reading "impl". Thanks

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.