Again (sorry): borrow *self as mutable

Sorry about this, I feel stupid especially since I see I'm not the only one with this issue, and I still can't figure out a solution based on the answers I've seen here :frowning:

So I have a struct:

pub struct DBWriter {
    conn: Connection,
    buffer: HashMap<String, (Info, u32)>,
}

It's a database connection and a data buffer of data so I can write in the database in a single transaction. This means that I have to access both, the connection and the buffer, so I have a function like this one:

fn save_game_buffer(&mut self) -> Result<()> {
    let tx = self.conn_mut().transaction()?;
    let mut buffer = &self.game_buffer;
    let values = buffer.values();

    for value in values {
        // ... then use the transaction to add the data
    }
    tx.commit();
    buffer.clear();
}

And as the more experienced rustacians here will notice, I'm getting the error:

cannot borrow `self.buffer` as immutable because it is also borrowed as mutable
immutable borrow occurs here

error[E0502]: cannot borrow `self.game_buffer` as immutable because it is also borrowed as mutable
   --> src/data/writer/sqlite.rs:226:26
    |
225 |         let tx = self.conn_mut().transaction()?;
    |                  ---- mutable borrow occurs here
226 |         let mut buffer = &self.game_buffer;
    |                          ^^^^^^^^^^^^^^^^^ immutable borrow occurs here
...
243 |         tx.commit()?;
    |         -- mutable borrow later used here

So, I've tried every combination possible, and I still can't figure out how to solve the problem. Thing is, I kind of understand that for memory safety the compiler wants me to prevent having two mutable references to self, but what I actually want to change are two variables independent from each other.

How can I iterate the values that belong to the struct, while using the database connection that is also owned by the struct?

Edit: Sorry, my brain currently hurts now trying to figure it out, although on paper this should look like a trivial problem and this is what frustrates me the most, I still feel I haven't switched my mindset on how to approach apparently simple problems in Rust.

Instead of calling a method like self.conn_mut(), that borrows the entire DBWriter, you will probably need to access individual fields, inline. For example, if conn_mut() just returns a reference to the conn field, you can write this as:

let tx = self.conn.transaction()?;

Similarly, rather than borrow a reference and holding it for a long time, which prevents other uses of the borrowed value in the meantime:

let buffer = &mut self.game_buffer;
// ...
// `self.game_buffer` is "locked" until `buffer` is no longer in use
// ...
buffer.clear();

...you should, if possible, borrow the value temporarily only when you need to access it:

self.game_buffer.clear(); // buffer is only borrowed for a single statement
2 Likes

OMG, you are right! I was overthinking using the conn_mut() function. It works, it was so simple. Thanks again :man_facepalming:

Edit: I think my biggest problem is that, coming from Java I'm used to go with getters for accessing fields.

You may find this article on handling interprocedural conflicts in Rust useful (I know I did). It also discusses how Rust could tighten a method's mutable borrow to a set of fields somehow in the future.

3 Likes

Yes, this is a common "issue" for newcomers; the Rust APIs have very specific semantics that don't exactly match the intuitive ones we have.

In your case, for instance, using an &mut self.conn is not the same as doing self.conn_mut() where:

impl ... {
    fn conn_mut (self: &'_ mut Self) -> &'_ mut Connection
    {
        &mut self.conn
    }
}

Indeed, despite the name, the looking, etc., Rust does not really have a notion of getters baked into the language (that's kind of the "issue" here), and when you write that fn conn_mut function definition / function signature (that's what Rust cares about when outside that function; it doesn't look at the function's implementation), you are just telling Rust that there is some computation involving &mut Self from which you derive, in some way or another, an &mut Connection. And by lifetime elision rules, the returned &mut Connection is only usable while *self is exclusively (&mut) borrowed, i.e., the obtained &mut Connection becomes unusable / invalidated the moment there is any interaction with self.

  • If you wonder why is that?, the answer is that this is done on purpose: this way the properties of the function signature and how it is used elsewhere, such as by downstream users, is not affected by the actual implementation of the function, which yields many desirable stability properties.

  • So, a way to interpret this error you were having is that Rust, conservatively, looks at a function signature, and assumes the "worst" / most constraining possible implementation, such as one where all the fields can be mutated during the function call or even by using the obtained &mut Connection. In a world with such an implementation, it would be indeed illegal to use any part of self, even "other fields".


In practice

  • Within the privacy boundaries, access the fields directly: getters are mainly used in Rust to express more fine-grained privacy (e.g. a publicly read-only field, or mutable only in certain ways).

  • Outside the privacy boundary, when pub matters,

    • In case of exclusive access: &mut

      prefer pub field to a pub fn field_mut getter: since offering mutable access to some field already gives a lot of power, unless you want to hide some internal implementation detail exposing the field directly will make it easier for users not to displease the borrow checker;

    • In case of shared access: &

      Keep using that kind of getter. The shared nature of the yielded reference means you won't have more trouble with this kind of getter than with a directly exposed field, and a directly exposed field has the drawback of allowing exclusive-and-thus-mutable access to the value, whereas this getter protects you from that.

  • In case a getter is unavoidable (e.g., lazily computed value), if you are yielding (and thus taking) &mut references, you can append references to the un-borrowed fields to the returned value, so as to make part of the API of the function that the other fields are available.

    Taking your conn_mut example:

    pub
    fn conn_mut_and_game_buffer (self: &'_ mut Self)
      -> (&'_ mut Connect, &'_ GameBuffer)
    {
        (&mut self.conn, &self.game_buffer)
    }
    // usage:
    let (conn_mut, game_buffer) = self.conn_mut_and_game_buffer();
    
2 Likes