我试图设计一个结构来携带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 &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
实际上并不需要移动;基本上它直接进入调用者的堆栈框架。
所以我不明白为什么它也不能将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仍然不会让我这样做。
答案 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
不能以同样的方式工作?
整个引用点是您不拥有引用的值。您返回Db
,make_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
,这对我来说似乎太糟糕了,但至少它让我把所有其他东西放在一起。