【问题标题】:Can we modify the execute around idiom to use a mutable self?我们可以修改 execute around idiom 以使用可变的 self 吗?
【发布时间】: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_usertransaction 函数采用&amp;mut self,而只是&amp;self,这感觉像是在撒谎。在我的版本中,我希望他们采用&amp;mut self,以便更清楚地表明他们正在更改应用程序状态。

如果我们尝试将&amp;self 的用法更改为&amp;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 的函数的签名更改为接受&amp;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_transactionend_transactionabort_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 - 我的函数几乎都使用 &amp;self 并使用 RefCell 使用内部突变来解决可变性的需求根据他们自己的数据。

有没有办法像柴油交易 API 一样包装 API 并获得我想要的可变性类型?

【问题讨论】:

    标签: rust transactions


    【解决方案1】:

    我设法解决了这个问题,注意到如果我们可以克隆包装的连接,我们可以将事务包装器编写为

    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。因此,我们可以存储Rc&lt;DieselConnection&gt;,而不是在DieselConnection 中存储DieselConnection,它确实实现了Clone

    完整的工作代码是:

    use std::rc::Rc;
    
    pub struct DieselConnection {}
    
    pub struct WrapperConnection {
        pub conn: Rc<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>,
        {
            let temp_conn = self.conn.clone();
            temp_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(())
        })
    }
    

    可以在 rust 游乐场here 中找到。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-15
      • 1970-01-01
      • 2020-01-05
      • 2013-10-13
      • 2017-11-11
      • 2018-09-01
      • 1970-01-01
      • 2021-11-10
      相关资源
      最近更新 更多