Sasha Kondrashov
Sasha Kondrashov

Reputation: 4570

Type annotations required for format! when parsing postgres results

I have some code that's supposed to get image filenames from a database and add them to a vector.

extern crate postgres;

use postgres::{Connection, TlsMode};

fn main() {
    let conn = Connection::connect(
        "postgres://postgres:password@localhost:5432/test",
        TlsMode::None,
    ).unwrap();
    let mut filenames = Vec::new();

    if let Ok(filename_results) = conn.query("SELECT filename FROM images", &[]) {
        for row in &filename_results {
            filenames.push(format!("{}.jpg", row.get(0)));
        }
    }

    println!("{:?}", filenames);
}

This fails with:

error[E0283]: type annotations required: cannot resolve `_: postgres::types::FromSql`
  --> src/main.rs:14:54
   |
14 |                 filenames.push(format!("{}.jpg", row.get(0)));
   |                                                      ^^^

I don't understand why Rust can't figure out the type in this context, though I've figured out a way to make it work. I'm wondering what the simplest/idiomatic way to tell format!() what types it should be expecting are, and why row.get(0) doesn't need a type annotation unless I slap a format!() around it. This is my best attempt at a solution:

for row in &filename_results {
    let filename: String = row.get(0);
    filenames.push(format!("{}.jpg", filename));
}

Upvotes: 1

Views: 671

Answers (1)

Dan Hulme
Dan Hulme

Reputation: 15290

Let's look at the signature of the function you're calling:

fn get<I, T>(&self, idx: I) -> T 
where
    I: RowIndex + Debug,
    T: FromSql,

That is, this function actually has two type parameters, I and T. It uses I as the type to index with. The argument you pass has this type. T is the return type. The constraints (the where clause) don't really matter here, but they specify that the argument type I has to be something postgres can use as a row index, and the return type T has to be something postgres can create from an SQL result.

Usually, Rust can infer the type parameters of functions. Argument types are usually easier to infer, because there's a value of the desired type right there. Even C++ can infer argument types! Return types are harder to infer because they depend on the context the function is called from, but Rust can often infer those too.

Let's look at your function call and the context it's used:

format!("{}.jpg", row.get(0))

Here's it's obvious that the argument is an integer, because it's a literal, and it's right there. There are rules for working out what integer types it could be, but in this case, it has to be usize because that's the only one the RowIndex trait is implemented for.

But what return type are you expecting? format! can take almost any type, so the compiler has no way to know what get needs to return. All it knows is that T has to have the FromSql trait. This is what the error message tells you:

error[E0283]: type annotations required: cannot resolve `_: postgres::types::FromSql`

Luckily, Rust has a syntax for explicitly passing function parameters to functions, so you don't have to rely on its type inference. Shepmaster wrote a good explanation of it in this answer to a similar question. Jumping straight to the answer, you can write row.get::<_, String>(0) to only specify the second type parameter, and let inference work on the first type parameter.

You specifically ask for a more idiomatic way to specify the type, and I think what you already have is more idiomatic. With the explicit type parameter, a reader still needs to understand the signature of get to know that String will be the return type. It's not always the case that the second type parameter will be the return type, and it's easy to get confused and specify them in the wrong order. By naming and type-annotating the result, you make it obvious what value the type annotation refers to.

let filename: String = row.get(0);
filenames.push(format!("{}.jpg", filename));

If you do want to write your code in the more functional style that Shepmaster suggested, you can still use this style:

let filenames = filename_results.map(|row| { let f: String = row.get(0); format!("{}.jpg", f) }).collect();

and break the "one-liner" across lines if that suits your taste.

Upvotes: 2

Related Questions