我正在尝试设计一个结构来携带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 &str或returning 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 回答
从这个功能开始:
由于lifetime elision,这相当于:
也就是说,只要字符串切片传入,
Db
struct _982953中所有引用的生命周期都会存在 . 只有结构保持字符串切片才有意义 .为了“解决”这个问题,我们可以尝试将生命周期分开:
现在这更没意义了,因为现在我们正在弥补一生 .
'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"问题的一部分,答案中说:
如果我们查看definition for Connection::transaction:
或the definition如果您不相信文档:
是的,
Transaction
保持对其父级Connection
的引用 . 现在我们看到Transaction
有一个对Connection
的引用,我们可以返回the other question看看如何解决问题:拆分结构以便嵌套反映生命周期 .这是一个非常冗长的说法:不,由于postgres包的实现,你不能创建一个包含数据库和该数据库事务的单一结构 . 据推测,箱子以这种方式实施以获得最佳性能 .
整个引用点是您不拥有引用的值 . 你返回
Db
并且make_db
的调用者会拥有它,但 what owns the thing that Db is referring to ?它从哪里来的?您不能返回对本地内容的引用,因为这会违反Rust的所有安全规则 . 如果您想转让所有权,那么您就是这样做的 .也可以看看
Is there any way to return a reference to a variable created in a function?
Return local String as a slice (&str)
使用the other answer,我将工作代码放在一起,让我将事务和所有准备好的语句捆绑在一起,并将它们一起传递:
据我了解,Rust希望保证
conn
只要db
,所以通过将conn
保持在"constructor"之外,词法结构可以确保它不会过早被删除 .我的结构仍然没有封装
conn
,这对我来说似乎太糟糕了,但至少它让我把所有其他东西放在一起 .