Need help to specify type for iterator generic in rusqlite


#1

Hi!

Just started with Rust a week ago, from a solid background in embedded C programming.
I would like to use the rusqlite crate (http://jgallagher.github.io/rusqlite/rusqlite/index.html) to write and read data from an sqlite3 db but immediately ran into some problems when trying to read from the db. This is my code:

extern crate rusqlite;

use rusqlite::Connection;

fn main() {
    let conn = Connection::open("db.sqlite3").unwrap();
    conn.execute("CREATE TABLE IF NOT EXISTS t1 (name TEXT PRIMARY KEY, nr INT)", &[]).unwrap();
    conn.execute("REPLACE INTO t1 VALUES (?, ?)", &[&"foo", &1]).unwrap();
    let mut stmt = conn.prepare("SELECT name FROM t1").unwrap();
    let rows = stmt.query_map(&[], |row| row.get(0));
}

When trying to build it I get:
src/main.rs:10:21: 10:30 error: unable to infer enough type information about _; type annotations or generic parameter binding required [E0282]
src/main.rs:10 let rows = stmt.query_map(&[], |row| row.get(0));
^~~~~~~~~
src/main.rs:10:21: 10:30 help: run rustc --explain E0282 to see a detailed explanation
error: aborting due to previous error
error: Could not compile rusqlite-test.

So the call the error message refers to is query_map. I tried to manually set the type of the rows binding to what I expected to be the return typ of query_map, but utterly failed.

Looking into http://jgallagher.github.io/rusqlite/rusqlite/struct.Statement.html#method.query_map I tried to use many different qualifiers but to no avail.

Could anyone explain to me how to proceed with this?

Thanks is advance!


#2

Row::get needs to know the type it gets. So for example row.get::<i64>(0). If they were some other annotations to know the return type of get, the annotation here won’t be needed. Please also note that the query_map returns an iterator wrapped in a Result, so you’ll need to add unwrap() + loop/collect() (like in examples) to get something usable.


#3

Looks like I was too slow to respond :slight_smile:

Additional tip: the way I use Rusqlite is I map a row to a struct, this way I don’t need to type explicit annotations since the compiler already has all the information it needs (thanks to the types of the fields). Also, with multiple rows I’m collecting into a Result<Vec<_>> so I don’t have to manually unwrap every entry.

Additional explanation about how Row::get: Rusqlite knows how to convert a few common types such as i64, String, etc. between Rust and SQLite automatically, you can get the full list by looking at the implementations of the FromSql trait.


#4

Thanks!

That fixed it, but I needed to make it row::get<i32, i64>(0). But I still don’t really understand how the compiler uses that extra information to solve the original problem, where it complained about the query_map function? Is it because it can now deduce exactly what kind of iterator to return from query_map or is it because it now understands exactly how query_map should call row.get(0) to build the iterator?


#5

Thank you!

I don’t follow exactly how you do that mapping, but it sounds like a good idea. When it comes to the FromSql trait, I found out about it, and that was also where I realized I couldn’t use an i64 for the number to retrieve.


#6

Sorry I couldn’t be more precise as I didn’t have the relevant code nearby (this is at work).
See below for an excerpt of the relevant code:

  1. definition of the User structure
  2. definition of a list method that maps each row to an instance of the User structure, and collects all rows to a Result<Vec, DbError>.
use rusqlite::{Connection, Error as DbError, Row};

pub struct User {
    /// use an option so we can insert a user without an explicit id
    id: Option<i64>,
    first_name: String,
    last_name: String
    // other fields etc.
}

pub fn list(connection: &Connection) -> Result<Vec<User>, DbError> {
    let mut stmt = try!(connection.prepare("SELECT * FROM users"));
    let rows = try!(stmt.query_map(&[], |row|
        User {
            id: Some(row.get(0)),
            first_name: row.get(1),
            last_name: row.get(2)
        }
    ));
    rows.collect()
}

Even better in my opinion is to define a map function as part of the implementation of your structure when you need to do the mapping more than once: for instance listing all users and finding a specific user.

impl User {
    fn map(row: &Row) -> User {
        User {
            id: Some(row.get(0)),
            first_name: row.get(1),
            last_name: row.get(2)
        }
    }
}

You can then call query_map with the function as a parameter like this: query_map(parameters, User::map)


#7

Oh right, I forgot about the index. row::get::<_, i64>(0) should be enough though.

Back to the original problem: The error message (which should be more specific, worth filing a bug) probably complains about the type of the closure. The closure’s type is defined in a where clause:

where F: FnMut(&Row) -> T

Rust doesn’t know which T to use, so it can’t call query_map. By specifying the type of get, you also specify the return type of the closure.

So the answer is “both” (but the error message was only about the former).


#8

Ahh, didn’t think of that - this is very good help and advice! Takes some practice to get away from the C way of looking at things.


#9

I see - that makes it clearer. Thanks again for pointing me in the right direction!