首页 文章

数据库连接包的Rust生存期

提问于
浏览
0

我正在尝试设计一个结构来携带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书(我已经失去了多少次),但我不确定如何在这里取得进展 . 有什么建议?

EDIT: 考虑到这一点,我还是不要告诉Rust,“ conn 只要生活 Db ” . 问题在于移动 conn ,但如果我没有返回指向堆栈分配内存的指针,例如:

#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 不会堆叠帧 .

所以我也没有't understand why it can'(也包括 conn )在调用者的堆栈框架中 . 然后不需要任何移动, tx 永远不会持有无效地址 . 我觉得Rust应该能够解决这个问题 . 我尝试添加一个终身提示,如下所示:

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

但是这会产生“意外的生命周期参数”错误 . 我可以接受Rust不能遵循逻辑,但我很好奇,如果有原因,原则上它不能 .

似乎将 conn 放在堆上应该可以解决我的问题,但我无法让它工作:

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

即使有 let conn = Box::new(Connection::connect(...)); ,Rust仍然告诉我"conn does not live long enough" . 有没有办法让这个工作 Box ,或者这是一个死胡同?

EDIT 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

    从这个功能开始:

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

    由于lifetime elision,这相当于:

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

    也就是说,只要字符串切片传入, Db struct _982953中所有引用的生命周期都会存在 . 只有结构保持字符串切片才有意义 .


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

    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?中进一步解释,搜索"something is really wrong with our creation function" .

    我们还在the other question中看到"Sometimes, I'm not even taking a reference of the value"问题的一部分,答案中说:

    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以同样的方式工作?

    整个引用点是您不拥有引用的值 . 你返回 Db 并且 make_db 的调用者会拥有它,但 what owns the thing that Db is referring to ?它从哪里来的?您不能返回对本地内容的引用,因为这会违反Rust的所有安全规则 . 如果您想转让所有权,那么您就是这样做的 .

    也可以看看

  • 3

    使用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 保持在"constructor"之外,词法结构可以确保它不会过早被删除 .

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

相关问题