rusqlite MappedRows

时间:2017-03-04 08:58:30

标签: rust

我正在尝试编写一个返回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()
}

我遇到了编译错误:

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]`

我在这里做错了什么?

我尝试将闭包直接传递给query_map,但我得到了相同的编译错误。

2 个答案:

答案 0 :(得分:5)

我将答案分为两部分,第一部分是关于如何在不考虑借用检查器的情况下修复返回类型,第二部分是关于为什么即使你修复了返回类型它也不起作用。

§1。

每个闭包都有一个唯一的匿名类型,因此c不能是调用者提供的任何类型F。这意味着这一行永远不会编译:

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

相反,类型应该从dump函数传播出来,例如:

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

Stable Rust没有提供命名该类型的方法。但我们可以在夜间使用“impl Trait”功能:

#![feature(conservative_impl_trait)]

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

此处impl F表示“我们将返回MappedRows<T>类型T: F,但我们不会指定T到底是什么。调用者应该准备好将满足F的任何内容视为T“的候选人。

由于你的闭包没有捕获任何变量,你实际上可以将c变成一个函数。我们可以命名一个函数指针类型,而不需要“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.

无论如何,如果我们使用“impl Trait”,因为MappedRows被用作迭代器,所以更恰当的说法是:

#![feature(conservative_impl_trait)]

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

(没有'c边界,编译器会抱怨E0564,似乎终生elision不适用于impl Trait了)

如果您遇到Stable Rust,则无法使用“impl Trait”功能。您可以将特征对象包装在Box中,但代价是堆分配和动态分派:

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

§2。

如果您想要return an independent closure or iterator,则上述修复工作正常。但是,如果您返回rusqlite::MappedRows,则无效。由于生存期问题,编译器不允许上述工作:

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>> {
   |                                                                                 ^

这是正确的。 MappedRows<F>实际上是MappedRows<'stmt, F>,这种类型只有在原始SQLite语句对象(生命周期为'stmt)超过它时才有效 - 因此编译器会抱怨stmt已经死了返回功能。

实际上,如果在我们迭代这些行之前删除该语句,我们将得到垃圾结果。糟糕!

我们需要做的是确保在删除语句之前读取所有行

您可以将行收集到一个向量中,从而将结果与语句取消关联,但代价是将所有内容存储在内存中:

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

或者反转控件,让dump接受一个函数,dump会在保持stmt活着的同时调用,但代价是调用语法很奇怪:

//                    ↓~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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));

或者将dump分成两个函数,让调用者保持语句的活动状态,代价是向用户公开一个中间结构:

#![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);
}

答案 1 :(得分:1)

这里的问题是你隐式尝试返回一个闭包,所以要查找解释和示例,你可以搜索它。

使用通用<F>意味着调用者决定F的具体类型和 函数dump

您希望实现的目标需要期待已久的功能impl trait