【发布时间】:2020-08-31 02:01:53
【问题描述】:
简短总结
我无法找到将 diesels 事务 API 包装到我自己的 API 中的方法,因为柴油机使用“执行周围”习语进行事务处理以及它的非 &mut self 参数。
这意味着我目前必须制作一些真正应该改变非&mut self 状态的函数并使用内部可变性。这感觉很难看,我想知道是否有解决方法。
加长版
rust 柴油库使用类似“execute around”这样的技术来处理事务。例如,要使用它在事务中将两个名称插入到数据库中,如下所示。
pub fn perform_insert_two_names_transactionally<C:Connection>(
conn: &C,
name1: &str,
name2: &str) {
conn.transaction::<_, Error, _>(|| {
diesel::insert_into(users)
.values(name.eq(name1))
.execute(&conn)?;
diesel::insert_into(users)
.values(name.eq(name2))
.execute(&conn)?;
Ok(())
})?;
}
transaction 函数的签名是
fn transaction<T, E, F>(&self, f: F) -> Result<T, E>
where
F: FnOnce() -> Result<T, E>,
E: From<Error>,
我们可以创建一个简化版本,这样我们就不需要使用柴油构建和定义表等。我们实际上只是将它用于类型和借用检查。
pub struct Connection {}
pub enum ConnectionError {}
impl Connection {
pub fn add_user(&self, name: &str) -> Result<(), ConnectionError> {
Ok(())
}
pub fn transaction<T, E, F>(&self, f: F) -> Result<T, E>
where
F: FnOnce() -> Result<T, E>,
{
self.begin_transaction();
let result = f();
if result.is_ok() {
self.end_transaction();
} else {
self.abort_transaction();
}
return result;
}
fn begin_transaction(&self) {}
fn end_transaction(&self) {}
fn abort_transaction(&self) {}
}
pub fn perform_insert_two_names_transactionally(
conn: &Connection,
name1: &str,
name2: &str,
) -> Result<(), ConnectionError> {
conn.transaction(|| {
conn.add_user(name1)?;
conn.add_user(name2)?;
Ok(())
})?;
Ok(())
}
要注意的关键是add_user 和transaction 函数不采用&mut self,而只是&self,这感觉像是在撒谎。在我的版本中,我希望他们采用&mut self,以便更清楚地表明他们正在更改应用程序状态。
如果我们尝试将&self 的用法更改为&mut self(请参阅此code),我们会收到以下错误:
error[E0501]: cannot borrow `*conn` as mutable because previous closure requires unique access
--> src/lib.rs:32:5
|
32 | conn.transaction(|| {
| ^ ----------- -- closure construction occurs here
| | |
| _____| first borrow later used by call
| |
33 | | conn.add_user(name1)?;
| | ---- first borrow occurs due to use of `conn` in closure
34 | | conn.add_user(name2)?;
35 | | Ok(())
36 | | })?;
| |______^ second borrow occurs here
我们可以通过将传递给transaction 的函数的签名更改为接受&mut Connection 来绕过这个约束,然后可以使用它来执行可变调用。
pub struct Connection {}
pub enum ConnectionError {}
impl Connection {
pub fn add_user(&mut self, name: &str) -> Result<(), ConnectionError> {
Ok(())
}
pub fn transaction<T, E, F>(&mut self, f: F) -> Result<T, E>
where
F: FnOnce(&mut Connection) -> Result<T, E>,
{
self.begin_transaction();
let result = f(self);
if result.is_ok() {
self.end_transaction();
} else {
self.abort_transaction();
}
return result;
}
fn begin_transaction(&mut self) {}
fn end_transaction(&mut self) {}
fn abort_transaction(&mut self) {}
}
pub fn perform_insert_two_names_transactionally(
conn: &mut Connection,
name1: &str,
name2: &str,
) -> Result<(), ConnectionError> {
conn.transaction(|conn| {
conn.add_user(name1)?;
conn.add_user(name2)?;
Ok(())
})?;
Ok(())
}
核心问题是在打包柴油交易时,我们无法访问begin_transaction、end_transaction 和abort_transaction——我们需要改用diesel::Connection::transaction 函数。
这个的简化版是
pub struct DieselConnection {}
pub struct WrapperConnection {
pub conn:DieselConnection
}
pub enum ConnectionError {}
impl DieselConnection {
pub fn add_user(&self, name: &str) -> Result<(), ConnectionError> {
Ok(())
}
pub fn transaction<T, E, F>(&self, f: F) -> Result<T, E>
where
F: FnOnce() -> Result<T, E>,
{
self.begin_transaction();
let result = f();
if result.is_ok() {
self.end_transaction();
} else {
self.abort_transaction();
}
return result;
}
fn begin_transaction(&self) {}
fn end_transaction(&self) {}
fn abort_transaction(&self) {}
}
impl WrapperConnection {
pub fn add_user(&mut self, name: &str) -> Result<(), ConnectionError> {
self.conn.add_user(name)
}
pub fn transaction<T, E, F>(&mut self, f: F) -> Result<T, E>
where
F: FnOnce(&mut WrapperConnection) -> Result<T, E>,
{
self.conn.transaction( || { f(self) } )
}
}
pub fn perform_insert_two_names_transactionally(
conn: &mut WrapperConnection,
name1: &str,
name2: &str,
) -> Result<(), ConnectionError> {
conn.transaction(|conn| {
conn.add_user(name1)?;
conn.add_user(name2)?;
Ok(())
})
}
也可通过playground 获得。
但是,这会产生以下错误:
error[E0500]: closure requires unique access to `self` but it is already borrowed
--> src/lib.rs:38:32
|
38 | self.conn.transaction( || { f(self) } )
| --------- ----------- ^^ ---- second borrow occurs due to use of `self` in closure
| | | |
| | | closure construction occurs here
| | first borrow later used by call
| borrow occurs here
error: aborting due to previous error; 1 warning emitted
再次强调,这是有道理的。
我认为这不能通过将连接置于 RefCell 或类似名称中来解决 - 但我很乐意被告知我错了。
目前我刚刚放弃以符合我的可变性期望的方式包装 API - 我的函数几乎都使用 &self 并使用 RefCell 使用内部突变来解决可变性的需求根据他们自己的数据。
有没有办法像柴油交易 API 一样包装 API 并获得我想要的可变性类型?
【问题讨论】:
标签: rust transactions