How do I pass a struct with type parameters as a function argument?

…衆ロ難τιáo~ 提交于 2019-12-22 18:10:02

问题


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?


回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/42064853/how-do-i-pass-a-struct-with-type-parameters-as-a-function-argument

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!