simao
simao

Reputation: 15549

Return type for rusqlite MappedRows

I am trying to write a method that returns a rusqlite::MappedRows:

pub fn dump<F>(&self) -> MappedRows<F>
    where F: FnMut(&Row) -> DateTime<UTC>
{
    let mut stmt =
        self.conn.prepare("SELECT created_at FROM work ORDER BY created_at ASC").unwrap();

    let c: F = |row: &Row| {
        let created_at: DateTime<UTC> = row.get(0);
        created_at
    };

    stmt.query_map(&[], c).unwrap()
}

I am getting stuck on a compiler error:

error[E0308]: mismatched types
  --> src/main.rs:70:20
   |
70 |           let c: F = |row: &Row| {
   |  ____________________^ starting here...
71 | |             let created_at: DateTime<UTC>  = row.get(0);
72 | |             created_at
73 | |         };
   | |_________^ ...ending here: expected type parameter, found closure
   |
   = note: expected type `F`
   = note:    found type `[closure@src/main.rs:70:20: 73:10]`

What am I doing wrong here?

I tried passing the closure directly to query_map but I get the same compiler error.

Upvotes: 2

Views: 2543

Answers (2)

kennytm
kennytm

Reputation: 523384

I'll divide the answer in two parts, the first about how to fix the return type without considering borrow-checker, the second about why it doesn't work even if you fixed the return type.


§1.

Every closure has a unique, anonymous type, so c cannot be of any type F the caller provides. That means this line will never compile:

let c: F = |row: &Row| { ... } // no, wrong, always.

Instead, the type should be propagated out from the dump function, i.e. something like:

//         ↓ no generics
pub fn dump(&self) -> MappedRows<“type of that c”> {
    ..
}

Stable Rust does not provide a way to name that type. But we could do so in nightly with the "impl Trait" feature:

#![feature(conservative_impl_trait)]

//                               ↓~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pub fn dump(&self) -> MappedRows<impl FnMut(&Row) -> DateTime<UTC>> {
    ..
}
// note: wrong, see §2.

The impl F here means that, “we are going to return a MappedRows<T> type where T: F, but we are not going to specify what exactly is T; the caller should be ready to treat anything satisfying F as a candidate of T”.

As your closure does not capture any variables, you could in fact turn c into a function. We could name a function pointer type, without needing "impl Trait".

//                               ↓~~~~~~~~~~~~~~~~~~~~~~~~
pub fn dump(&self) -> MappedRows<fn(&Row) -> DateTime<UTC>> {
    let mut stmt = self.conn.prepare("SELECT created_at FROM work ORDER BY created_at ASC").unwrap();

    fn c(row: &Row) -> DateTime<UTC> {
        row.get(0)
    }

    stmt.query_map(&[], c as fn(&Row) -> DateTime<UTC>).unwrap()
}
// note: wrong, see §2.

Anyway, if we do use "impl Trait", since MappedRows is used as an Iterator, it is more appropriate to just say so:

#![feature(conservative_impl_trait)]

pub fn dump<'c>(&'c self) -> impl Iterator<Item = Result<DateTime<UTC>>> + 'c {
    ..
}
// note: wrong, see §2.

(without the 'c bounds the compiler will complain E0564, seems lifetime elision doesn't work with impl Trait yet)

If you are stuck with Stable Rust, you cannot use the "impl Trait" feature. You could wrap the trait object in a Box, at the cost of heap allocation and dynamic dispatch:

pub fn dump(&self) -> Box<Iterator<Item = Result<DateTime<UTC>>>> {
    ...
    Box::new(stmt.query_map(&[], c).unwrap())
}
// note: wrong, see §2.

§2.

The above fix works if you want to, say, just return an independent closure or iterator. But it does not work if you return rusqlite::MappedRows. The compiler will not allow the above to work due to lifetime issue:

error: `stmt` does not live long enough
  --> 1.rs:23:9
   |
23 |         stmt.query_map(&[], c).unwrap()
   |         ^^^^ does not live long enough
24 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the body at 15:80...
  --> 1.rs:15:81
   |
15 | pub fn dump(conn: &Connection) -> MappedRows<impl FnMut(&Row) -> DateTime<UTC>> {
   |                                                                                 ^

And this is correct. MappedRows<F> is actually MappedRows<'stmt, F>, this type is valid only when the original SQLite statement object (having 'stmt lifetime) outlives it — thus the compiler complains that stmt is dead when you return the function.

Indeed, if the statement is dropped before we iterate on those rows, we will get garbage results. Bad!

What we need to do is to make sure all rows are read before dropping the statement.

You could collect the rows into a vector, thus disassociating the result from the statement, at the cost of storing everything in memory:

//                    ↓~~~~~~~~~~~~~~~~~~~~~~~~~
pub fn dump(&self) -> Vec<Result<DateTime<UTC>>> {
    ..
    let it = stmt.query_map(&[], c).unwrap();
    it.collect()
}

Or invert the control, let dump accept a function, which dump will call while keeping stmt alive, at the cost of making the calling syntax weird:

//                    ↓~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pub fn dump<F>(&self, mut f: F) where F: FnMut(Result<DateTime<UTC>>) {
    ...
    for res in stmt.query_map(&[], c).unwrap() {
        f(res);
    }
}

x.dump(|res| println!("{:?}", res));

Or split dump into two functions, and let the caller keep the statement alive, at the cost of exposing an intermediate construct to the user:

#![feature(conservative_impl_trait)]

pub fn create_dump_statement(&self) -> Statement {
    self.conn.prepare("SELECT '2017-03-01 12:34:56'").unwrap()
}

pub fn dump<'s>(&self, stmt: &'s mut Statement) -> impl Iterator<Item = Result<DateTime<UTC>>> + 's {
    stmt.query_map(&[], |row| row.get(0)).unwrap()
}

...

let mut stmt = x.create_dump_statement();
for res in x.dump(&mut stmt) {
    println!("{:?}", res);
}

Upvotes: 10

dippi
dippi

Reputation: 153

The issue here is that you are implicitly trying to return a closure, so to find explanations and examples you can search for that.

The use of the generic <F> means that the caller decides the concrete type of F and not the function dump.

What you would like to achieve instead requires the long awaited feature impl trait.

Upvotes: 1

Related Questions