2

How do I pass an instance of EcsClient with the signature impl<P, D> EcsClient<P, D> where P: ProvideAwsCredentials, D: DispatchSignedRequest to a function as a reference in Rust? My attempt is thus:

extern crate rusoto;

use std::default::Default;

use rusoto::{ DefaultCredentialsProvider, Region };
use rusoto::ecs::{ EcsClient };
use rusoto::default_tls_client;

fn get_task_definition_revisions(client: &EcsClient) {
    // Use EscClient instance here
}

fn main() {
    let provider = DefaultCredentialsProvider::new().unwrap();
    let client = EcsClient::new(default_tls_client().unwrap(), provider, Region::EuWest1).unwrap();

    get_task_definition_revisions(&client);

}

This gives me the following error:

error[E0243]: wrong number of type arguments: expected 2, found 0
 --> src/main.rs:9:43
  |
9 | fn get_task_definition_revisions(client: &EcsClient) {
  |                                           ^^^^^^^^^ expected 2 type arguments

My attempted fix for this is such:

extern crate rusoto;

use std::default::Default;

use rusoto::{
    DefaultCredentialsProvider,
    Region,
    ProvideAwsCredentials,
    DispatchSignedRequest
};
use rusoto::ecs::{ EcsClient, ListTaskDefinitionsRequest };
use rusoto::default_tls_client;

fn get_task_definition_revisions(client: &EcsClient<ProvideAwsCredentials, DispatchSignedRequest>) {
    // Use EcsClient instance here
}

fn main() {
    let provider = DefaultCredentialsProvider::new().unwrap();
    let client = EcsClient::new(default_tls_client().unwrap(), provider, Region::EuWest1);

    get_task_definition_revisions(&client);
}

Which gives me:

error[E0277]: the trait bound `rusoto::ProvideAwsCredentials + 'static: std::marker::Sized` is not satisfied
  --> src/main.rs:14:1
   |
14 |   fn get_task_definition_revisions(client: &EcsClient<P, D>) {
   |  _^ starting here...
15 | |  let defs = client.list_task_definitions(&ListTaskDefinitionsRequest {
16 | |      family_prefix: None,
17 | |      max_results: None,
18 | |      next_token: None,
19 | |      sort: None,
20 | |      status: Some("ACTIVE".to_string()),
21 | |  });
22 | | }
   | |_^ ...ending here: the trait `std::marker::Sized` is not implemented for `rusoto::ProvideAwsCredentials + 'static`
   |
   = note: `rusoto::ProvideAwsCredentials + 'static` does not have a constant size known at compile-time
   = note: required by `rusoto::ecs::EcsClient`

error[E0277]: the trait bound `rusoto::DispatchSignedRequest + 'static: std::marker::Sized` is not satisfied
  --> src/main.rs:14:1
   |
14 |   fn get_task_definition_revisions(client: &EcsClient<P, D>) {
   |  _^ starting here...
15 | |  let defs = client.list_task_definitions(&ListTaskDefinitionsRequest {
16 | |      family_prefix: None,
17 | |      max_results: None,
18 | |      next_token: None,
19 | |      sort: None,
20 | |      status: Some("ACTIVE".to_string()),
21 | |  });
22 | | }
   | |_^ ...ending here: the trait `std::marker::Sized` is not implemented for `rusoto::DispatchSignedRequest + 'static`
   |
   = note: `rusoto::DispatchSignedRequest + 'static` does not have a constant size known at compile-time
   = note: required by `rusoto::ecs::EcsClient`

This feels like a rabbit hole I shouldn't be going down.

I've also tried changing the function signature to accept generics, however the EcsClient is a struct not a trait. Googling doesn't provide much help because I don't know the correct terms to search for.

This question seems to imply that I should be able to declare a function like fn my_func(client: &EcsClient) { ... } and it will work, so why doesn't the above example?

Community
  • 1
  • 1
Bojangles
  • 99,427
  • 50
  • 170
  • 208
  • Does `fn get_task_definition_revisions

    (client: &EcsClient

    ) {...}` work? You need to declare any type parameters you use in the signature.

    – Chris Emerson Feb 06 '17 at 10:06
  • @ChrisEmerson Unfortunately not. I get the errors `the trait rusoto::ProvideAwsCredentials is not implemented for ProvideAwsCredentials` and `the trait rusoto::ProvideAwsCredentials is not implemented for ProvideAwsCredentials`. What should the calling signature look like? I still have it as `get_task_definition_revisions(&client);` – Bojangles Feb 06 '17 at 10:15

2 Answers2

7

The problem is that EcsClient is not a type, it's a blueprint to build a type (also known as "type constructor").

As a result, you cannot use just EcsClient when a type is required, be it in functions or for struct members; instead, each time, you must use it to build a type by specifying its generic parameters.

Thus, the first step is to introduce the type parameters:

fn get_task_definition_revisions<P, D>(client: &EcsClient<P, D>) {}

Now, however, the compiler will complain that the P and D are insufficiently constrained: EcsClient only accept a very specific kind of P and D!

The next step, thus, is to look-up the bounds that are specified for P and D in the definition of EcsClient and apply them. It's just copy/paste at this point:

fn get_task_definition_revisions<P, D>(client: &EcsClient<P, D>)
    where P: ProvideAwsCredentials,
          D: DispatchSignedRequest
{
}

And then you're golden.

If you need more capabilities of P or D for this specific function, feel free to constrain them adequately by adding more bounds using +:

fn get_task_definition_revisions<P, D>(client: &EcsClient<P, D>)
    where P: ProvideAwsCredentials + 'static,
          D: DispatchSignedRequest
{
}

If you wonder why Rust chose to have you repeat the bounds for P and D when it could perfectly infer them, it's because it cares about you. More specifically, it cares about you 6 months from now, and the next maintainer to come. So, taking the stance that you write once and read many, it forces you to copy the bounds so that later you don't have to wonder what they are, and drill down recursively in each type/function used to painfully aggregate all the pieces. In Rust, the next time you read the function, you'll have all the information right there.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Thank you very much, this clarifies the problem for me. I ended up writing `fn get_task_definition_revisions(client: &EcsClient

    ) {`. Is this equivalent? I read that `where` is helpful when one wants to use many types, but as I know I'm only ever going to use `ProvideAwsCredentials` and `DispatchSignedRequest` is it acceptable to use the syntax that I did?

    – Bojangles Feb 06 '17 at 13:43
  • 1
    @Bojangles: Both are strictly equivalent. I myself prefer using `where` clauses to have the list of arguments close to the function name. – Matthieu M. Feb 06 '17 at 14:40
  • this sounds like it violates DRY principles, and if a struct changes its trait bounds, i have to change all functions that take the struct as an argument... – confused00 Nov 24 '18 at 20:42
  • 1
    @confused00: It doesn't, actually, violates DRY. DRY is not about the absence of repetition for its own sake, it's about the absence of duplication which will later down the road cause you to fix one instance of the "pattern" and not the other. Since the Rust compiler enforces that the bounds be explicit in all use sites, you cannot forget to update one, and therefore the repetition does not cause latent bugs. – Matthieu M. Nov 25 '18 at 12:02
  • if the original trait bounds relax, then the compiler won't enforce you to also relax your functions, no? – confused00 Nov 25 '18 at 12:39
  • @confused00: I don't think so, no. The function would still be correct, albeit possibly unnecessarily constrained; since you could voluntarily over-constrain a function definition to leave yourself room for future backward-compatible changes, the compiler would not be allowed to reject the code. There may be warnings, from either compiler or linters, to help catch such cases, but I've never run into this case (or never realized I did). Fortunately, it's an easily-fixed mistake, as loosening a function requirement is backward-compatible API-wise. – Matthieu M. Nov 25 '18 at 13:02
1

I was understanding the syntax incorrectly. It seems that P and D are placeholders, like T would be. I need to specify what those types are, so the signature now looks like this:

fn get_task_definition_revisions<P: ProvideAwsCredentials, D: DispatchSignedRequest>(client: &EcsClient<P, D>) {
    ...
}

I don't use P or D in the function body, but they must be declared.

The full example now looks like:

extern crate rusoto;

use std::default::Default;

use rusoto::{
    DefaultCredentialsProvider,
    Region,
    ProvideAwsCredentials,
    DispatchSignedRequest
};
use rusoto::ecs::{ StringList, EcsClient, ListTaskDefinitionsRequest };
use rusoto::default_tls_client;

fn get_task_definition_revisions<P: ProvideAwsCredentials, D: DispatchSignedRequest>(client: &EcsClient<P, D>) {
    let res = client.list_task_definitions(&ListTaskDefinitionsRequest {
        family_prefix: None,
        max_results: None,
        next_token: None,
        sort: None,
        status: Some("ACTIVE".to_string()),
    });

    // ...
}

fn main() {
    let provider = DefaultCredentialsProvider::new().unwrap();
    let client = EcsClient::new(default_tls_client().unwrap(), provider, Region::EuWest1);

    get_task_definition_revisions(&client);
}

I'm still not entirely sure why this works or is required but I hope this answer helps someone else.

Bojangles
  • 99,427
  • 50
  • 170
  • 208
  • 1
    Note: you can also say `fn get_task_definition_revisions

    (client: &EcsClient

    ) where P: ProvideAwsCredentials, D: DispatchSignedRequest { }` which makes the signature more readable (IMHO)

    – Matthieu M. Feb 06 '17 at 12:19