我正在尝试编写一个返回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
,但我得到了相同的编译错误。
答案 0 :(得分:5)
我将答案分为两部分,第一部分是关于如何在不考虑借用检查器的情况下修复返回类型,第二部分是关于为什么即使你修复了返回类型它也不起作用。
每个闭包都有一个唯一的匿名类型,因此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.
如果您想要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
。