数据库连接包

时间:2017-02-10 00:52:55

标签: rust

我试图设计一个结构来携带Postgres连接,事务和一堆预处理语句,然后重复执行预处理语句。但我遇到了终生难题。这就是我所拥有的:

extern crate postgres;

use postgres::{Connection, TlsMode};
use postgres::transaction::Transaction;
use postgres::stmt::Statement;

pub struct Db<'a> {
    conn: Connection,
    tx: Transaction<'a>,
    insert_user: Statement<'a>,
}

fn make_db(url: &str) -> Db {
    let conn = Connection::connect(url, TlsMode::None).unwrap();
    let tx = conn.transaction().unwrap();
    let insert_user = tx.prepare("INSERT INTO users VALUES ($1)").unwrap();
    Db {
        conn: conn,
        tx: tx,
        insert_user: insert_user,
    }
}

pub fn main() {
    let db = make_db("postgres://paul@localhost/t");
    for u in &["foo", "bar"] {
        db.insert_user.execute(&[&u]);
    }
    db.tx.commit().unwrap();
}

这是我得到的错误(在Rust 1.15.0稳定版上):

error: `conn` does not live long enough
  --> src/main.rs:15:14
   |
15 |     let tx = conn.transaction().unwrap();
   |              ^^^^ does not live long enough
...
22 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the body at 13:28...
  --> src/main.rs:13:29
   |
13 | fn make_db(url: &str) -> Db {
   |                             ^

我已经阅读了Rust书(我已经失去了多少次数),但我不确定如何在这里取得进展。有什么建议吗?

编辑:更多地考虑这一点我仍然不明白为什么原则上我不能告诉Rust,&#34; conn只要生活Db确实&#34;。问题在于移动conn,但如果我不移动它会怎么样?我理解为什么在C中你不能返回指向堆栈分配内存的指针,例如:

#include <stdio.h>

int *build_array() {
  int ar[] = {1,2,3};
  return ar;
}

int main() {
  int *ar = build_array();
  printf("%d\n", ar[1]);
}

我得到的结果与Rust returning a &strreturning a vec slice类似。

但在Rust中你可以这样做:

#[derive(Debug)]
struct S {
    ar: Vec<i32>,
}

fn build_array() -> S {
    let v = vec![1, 2, 3];
    S { ar: v }
}

fn main() {
    let s = build_array();
    println!("{:?}", s);
}

我的理解是,Rust很聪明,所以返回S实际上并不需要移动;基本上它直接进入调用者的堆栈框架。

所以我不明白为什么它也不能将Db(包括conn)放入调用者的堆栈框架中。然后不需要任何移动,tx永远不会持有无效地址。我觉得Rust应该能够解决这个问题。我尝试添加一个终身提示,如下所示:

pub struct Db<'a> {
    conn: Connection<'a>,
    tx: Transaction<'a>,
    insert_user: Statement<'a>,
}

但是这给出了一个意外的生命周期参数&#34;错误。我可以接受Rust不能遵循这个逻辑,但我很好奇是否有理由说原则上它不能。

确实似乎将conn放在堆上应该可以解决我的问题,但我无法解决这个问题:

pub struct Db<'a> {
    conn: Box<Connection>,
    tx: Transaction<'a>,
    insert_user: Statement<'a>,
}

即使有let conn = Box::new(Connection::connect(...));,Rust仍然告诉我&#34; conn活得不够长&#34;。是否有某种方法可以使Box使用,或者这是一个死胡同?

编辑2:我也尝试使用宏来避免任何额外的堆栈帧:

extern crate postgres;

use postgres::{Connection, TlsMode};
use postgres::transaction::Transaction;
use postgres::stmt::Statement;

pub struct Db<'a> {
    conn: Connection,
    tx: Transaction<'a>,
    insert_user: Statement<'a>,
}

macro_rules! make_db {
      ( $x:expr ) => {
        {
          let conn = Connection::connect($x, TlsMode::None).unwrap();
          let tx = conn.transaction().unwrap();
          let insert_user = tx.prepare("INSERT INTO users VALUES ($1)").unwrap();
          Db {
            conn: conn,
            tx: tx,
            insert_user: insert_user,
          }
        }
      }
    }


pub fn main() {
    let db = make_db!("postgres://paul@localhost/t");
    for u in &["foo", "bar"] {
        db.insert_user.execute(&[&u]);
    }
    db.tx.commit().unwrap();
}

但这仍然告诉我,康恩的活得不够久。似乎将它移动到结构中应该真的不需要任何真正的RAM更改,但Rust仍然不会让我这样做。

2 个答案:

答案 0 :(得分:3)

从这个功能开始:

fn make_db(url: &str) -> Db {
    unimplemented!()
}

由于lifetime elision,这相当于:

fn make_db<'a>(url: &'a str) -> Db<'a> {
    unimplemented!()
}

也就是说,只要字符串切片传入,Db结构内的所有引用的生命周期必须。只有结构保持在字符串切片。

为了“解决”这个问题,我们可以尝试将生命周期分开:

fn make_db<'a, 'b>(url: &'a str) -> Db<'b> {
    unimplemented!()
}

现在这使更没意义,因为现在我们正在弥补一生。 'b来自何处?如果make_db的调用者决定通用生存期参数'b的具体生命周期应为'static,会发生什么?这在Why can't I store a value and a reference to that value in the same struct?中进一步解释,搜索“我们的创建函数确实存在问题”。

我们也会在the other question中看到问题的一部分“有时,我甚至没有参考价值”,这在答案中说:

  

Child实例包含对创建它的Parent的引用,

如果我们查看definition for Connection::transaction

fn transaction<'a>(&'a self) -> Result<Transaction<'a>>

the definition如果您不相信文档:

pub struct Transaction<'conn> {
    conn: &'conn Connection,
    depth: u32,
    savepoint_name: Option<String>,
    commit: Cell<bool>,
    finished: bool,
}

是的,Transaction会保留对其父Connection的引用。现在我们看到Transaction引用了Connection,我们可以返回the other question看看如何解决问题:拆分结构以便嵌套反映生命周期。

这是一种非常冗长的说法:不,由于postgres包的实现,你不能创建一个包含数据库和该数据库事务的单一结构。据推测,箱子是以这种方式实现的,以获得最佳性能。

  

我不明白为什么[返回Db<'b>]没有意义。通常,当一个函数返回一个东西时,只要它被分配给某个东西,它就会存在。为什么-> Db不能以同样的方式工作?

整个引用点是您不拥有引用的值。您返回Dbmake_db的来电者将拥有该权限,但拥有Db所指的内容的内容是什么?它从哪里来的?您不能返回对本地内容的引用,因为这会违反Rust的所有安全规则。如果你想转让所有权,你就是这么做的。

另见

答案 1 :(得分:1)

使用the other answer,我将工作代码放在一起,让我将事务和所有准备好的语句捆绑在一起,并将它们一起传递:

extern crate postgres;

use postgres::{Connection, TlsMode};
use postgres::transaction::Transaction;
use postgres::stmt::Statement;

pub struct Db<'a> {
    tx: Transaction<'a>,
    insert_user: Statement<'a>,
}

fn make_db(conn: &Connection) -> Db {
    let tx = conn.transaction().unwrap();
    let insert_user = tx.prepare("INSERT INTO users VALUES ($1)").unwrap();
    Db {
        tx: tx,
        insert_user: insert_user,
    }
}

pub fn main() {
    let conn = Connection::connect("postgres://paul@localhost/t", TlsMode::None).unwrap();
    let db = make_db(&conn);
    for u in &["foo", "bar"] {
        db.insert_user.execute(&[&u]);
    }
    db.tx.commit().unwrap();
}

据我了解,Rust希望保证conn只要db生存,所以通过将conn保留在“构造函数”之外,词法结构可以确保它赢了过早删除。

我的结构仍然没有封装conn,这对我来说似乎太糟糕了,但至少它让我把所有其他东西放在一起。