如何解决我们可以修改成语周围的执行以使用可变的self吗?
简短摘要
我无法找到将diesel
的交易API封装在我自己的API中的方法,这是因为柴油机使用了“执行四周”的习惯用法来进行交易处理,加上它的非&mut self
论点。
这意味着我目前必须制作一些实际上应该使状态也变为非&mut self
并使用内部可变性的函数。这感觉很难看,我想知道是否有解决方法。
长版
防锈柴油库使用类似“执行周围”的习惯用法来处理事务。例如,使用它在事务内的数据库中插入两个名称看起来像这样。
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>
where
F: FnOnce() -> Result<T,{
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,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,F>(&mut self,E>
where
F: FnOnce(&mut Connection) -> Result<T,{
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,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,{
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,ConnectionError> {
self.conn.add_user(name)
}
pub fn transaction<T,E>
where
F: FnOnce(&mut WrapperConnection) -> Result<T,{
self.conn.transaction( || { f(self) } )
}
}
pub fn perform_insert_two_names_transactionally(
conn: &mut WrapperConnection,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
,而使用{ {1}}使用他们自己的数据。
是否可以通过某种方式包装像柴油交易API这样的API并获得我想要的可变性类型?
解决方法
我设法解决了这一问题,方法是:如果我们可以克隆包装的连接,则可以将事务包装器写为
impl WrapperConnection {
pub fn transaction<T,E,F>(&mut self,f: F) -> Result<T,E>
where
F: FnOnce(&mut WrapperConnection) -> Result<T,E>,{
let conn_copy = self.conn.clone();
conn_copy.transaction( || { f(self) } )
}
}
(如果您将#[derive(Clone)]
添加到DieselConnection
将会编译)
但是实际上,基本连接没有实现Clone
。因此,我们可以存储实现了DieselConnection
的{{1}}而不是将WrapperConnection
存储在Rc<DieselConnection>
中。
完整的工作代码为:
Clone
可以在生锈的游乐场here中找到。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。