Search code examples
typesrustcapnproto

Why do I get the error "expected type argument" when I annotate a type in a Rust function?


I am playing around with Rust's capnproto library. Because Rust can infer types in some situations, I can do things like this:

let mut message = ::capnp::message::Builder::new_default();

Without having to know the type of message. If I want to pass a reference to message into a function, I now need to know what message is to let the function know what to expect.

Is there a convenient way to do this in general?

So far I have done the following:

let testing: () = message;

which fails with the compiler error:

error[E0308]: mismatched types
   --> src/main.rs:197:18
    |
197 |                 let temp: () = message;
    |                           ^^^^^^^ expected (), found struct `capnp::message::Builder`

But when I type annotate my function as follows:

fn example_fn(message: capnp::message::Builder) {...}

I get an error like:

error[E0243]: wrong number of type arguments: expected 1, found 0
  --> src/main.rs:72:32
   |
72 | fn dump_capnp_to_file(message: capnp::message::Builder, filename: &str) {
   |                                ^^^^^^^^^^^^^^^^^^^^^^^ expected 1 type argument

error: aborting due to previous error

I'm very new to Rust coming from a C++ background; sorry if this a rookie question!


Solution

  • Rust will not infer types in function parameter position. This is by design, as the Rust language FAQ states:

    Why aren't function signatures inferred?

    In Rust, declarations tend to come with explicit types, while actual code has its types inferred. There are several reasons for this design:

    • Mandatory declaration signatures help enforce interface stability at both the module and crate level.

    • Signatures improve code comprehension for the programmer, eliminating the need for an IDE running an inference algorithm across an entire crate to be able to guess at a function’s argument types; it’s always explicit and nearby.

    • Mechanically, it simplifies the inference algorithm, as inference only requires looking at one function at a time.

    Since capnp::message::Builder<A> takes a type parameter A, you need to qualify the type of the parameter by giving A a value:

    fn dump_capnp_to_file(message: capnp::message::Builder<SomeType>, filename: String) {
    //                                                    ^^^^^^^^^^
    

    or else make your function also generic so it can accept any type A:

    fn dump_capnp_to_file<A>(message: capnp::message::Builder<A>, filename: String) {
    //                   ^^^                                 ^^^
    

    Putting bounds on A

    If you take the last option, you might want additional trait bounds to allow you to do different things with message inside the function. For example, you might want to send message to another thread, which requires that Builder<A> implement Send. Builder has the following impl (reference):

    impl <A> Send for Builder<A> where A: Send + Allocator
    

    which means that Builder<A> can implement Send, but only when A implements Send and Allocator. You can make that your own bound (requirement) on A:

    fn dump_capnp_to_file<A>(message: capnp::message::Builder<A>, filename: String)
        where A: Send + Allocator
    {
        // multi-threaded code...
    }
    

    Alternatively (and maybe slightly better), bound Builder<A> with Send directly:

    fn dump_capnp_to_file<A>(message: capnp::message::Builder<A>, filename: String)
        where capnp::message::Builder<A>: Send
    

    Then you will only be able to call dump_capnp_to_file on a Builder that implements Send.