Expressing lifetimes of a variable pair within multiple closures

时间:2018-02-03 09:59:49

标签: stream rust lifetime borrow-checker

I'm struggling to express my code in a way to please the borrow-checker.

I have a function create_task which creates a future of some database operations. There's a stream of values where each element needs to be inserted to a database within a transaction. The problem is sharing the transaction between multiple closures as it has also mutably borrowed the connection object.

#![feature(conservative_impl_trait)]

extern crate futures;
extern crate rusqlite;

use futures::prelude::*;
use futures::{future, stream};
use rusqlite::Connection;

fn main() {
    let task = create_task();
    task.wait().unwrap();
}

fn create_task() -> impl Future<Item = (), Error = ()> {
    let mut conn = Connection::open("temp.db").unwrap();
    conn.execute("CREATE TABLE IF NOT EXISTS temp (val INTEGER)", &[]).unwrap();

    // tx takes a mut ref to conn!
    let tx = conn.transaction().unwrap();

    stream::iter_ok::<_, ()>(vec![1, 2, 3])
        .for_each(|val| {
            // tx borrowed here!
            tx.execute("INSERT INTO temp (val) VALUES (?1)", &[&val]).unwrap();
            future::ok(())
        })
        .map(|_| {
            // tx moved/consumed here!
            tx.commit().unwrap();
        })
}

There are multiple issues with the code:

  • conn does not live long enough. It needs to be also moved to the closures. Perhaps as an Rc<Connection> because of two closures?
  • conn can't be simply shared as an Rc because of mutability requirements. Perhaps Rc<RefCell<Connection>> is a more suitable type?
  • the borrow-checker does not know that the borrow to tx ends after the first for_each closure, therefore it cannot be moved to the second map closure. Once again, moving it as Rc<Transaction> to both closures might be reasonable?

I've been fiddling around with those ideas and know that the desired lifetimes are possible and make sense, but have not been able to express my code to the compiler in a correct way.

1 个答案:

答案 0 :(得分:2)

我相信你的第一个问题是你还没有完全掌握未来的懒惰。您正在Connection内创建create_task,引用它,将该引用放入流/未来,然后尝试返回该未来。 此时没有任何闭包执行

cannot return a reference to a value created in a function。不要尝试store the transaction and the connection in the same struct, either

相反,接受对Connection的引用并返回包含该生命周期的Future

下一个问题是编译器不知道如何调用闭包或以什么顺序调用闭包。而不是试图关闭交易,让它&#34;流动&#34;从一个到另一个,让所有权制度确保它始终在正确的位置。

#![feature(conservative_impl_trait)]

extern crate futures;
extern crate rusqlite;

use futures::prelude::*;
use futures::{future, stream};
use rusqlite::Connection;

fn main() {
    let mut conn = Connection::open("temp.db").unwrap();
    conn.execute("CREATE TABLE IF NOT EXISTS temp (val INTEGER)", &[]).unwrap();

    let task = create_task(&mut conn);
    task.wait().unwrap();
}

fn create_task<'a>(conn: &'a mut rusqlite::Connection) -> impl Future<Item = (), Error = ()> + 'a {
    let tx = conn.transaction().unwrap();
    stream::iter_ok::<_, ()>(vec![1, 2, 3])
        .fold(tx, |tx, val| {
            tx.execute("INSERT INTO temp (val) VALUES (?1)", &[&val]).unwrap();
            future::ok(tx)
        })
        .map(move |tx| {
            tx.commit().unwrap();
        })
}

一个巨大的警告:如果execute不是异步的,那么真的不应该在未来这样的情况下使用它。任何阻止操作都会导致您的所有期货停滞不前。您可能应该在单独的线程/线程池上运行同步工作负载。

另见: