Exclusive references and dereference operator

I have some experience with Rust, generally understand ownership and borrowing, but having javascript background, I just can't wrap my head around the dereference operator.

I use an ORM that lets me run a transaction in a closure. The closure receives a mutable reference to the Transaction struct, this struct can then be used in database operations. It looks something like this:

let result = db.transaction(async |transaction| {
    query!("SELECT * FROM user").fetch_one(transaction).await?
}).await?;

transaction here is of type &mut Transaction. So if there are 2 or more queries in a transaction, the code won't compile:

let result = db.transaction(async |transaction| {
    query!("INSERT INTO user VALUES ('foo', 'bar', 'baz')").execute(transaction).await?;
    query!("SELECT * FROM user").fetch_one(transaction).await?
}).await?;

Which seems reasonable from the compiler's point of view: &mut Transaction doesn't implement Copy, so it is moved in the execute() call. Looks like a bad API design by the library authors that makes the transaction() method unusable. However, with this little trick the code compiles fine:

let result = db.transaction(async |transaction| {
    query!("INSERT INTO user VALUES ('foo', 'bar', 'baz')").execute(&mut *transaction).await?;
    query!("SELECT * FROM user").fetch_one(&mut *transaction).await?
}).await?;

So the question is, why would the compiler allow me to do it, effectively create two mutable references to the same thing?

The basic concept is the concept of re-borrowing. Think about borrowing the borrow. If transaction is some &'long mut Transaction, then you could create a &'short mut &'long mut Transaction from this.

In fact, you could create multiple such borrows-of-borrows, as long as they don’t exist at the same time. If the lifetime 'short2 starts after the lifetime 'short1 ends, you can first create a &'short1 mut &'long mut Transaction and later in the code create a &'short2 mut &'long mut Transaction.

Now to fully understand re-borrowing, you also need to know that Rust allows you to go from &'short mut &'long mut T to &'short mut T. The idea why this is okay is that the type &'short mut &'long mut T does give exclusive access to the &'long mut T for the duration of the lifetime 'short. And exclusive access to the &'long mut T also gives exclusive access to the underlying T.

If you apply these &'s mut &'l mut T to &'s mut T conversions to the types above, this means it is okay to first create a &'short1 mut Transaction and later a &'short2 mut Transaction. These two don’t exist at the same time, but they do exist at the same time as the initial &'long mut Transaction reference. That reference however counts as borrowed for the duration while each of the re-borrows &'short1 mut Transaction or &'short1 mut Transaction are alive, so it’s not possible to use/access two mutable references at the same time this way.

Re-borrows often happen automatically and implicitly, your code was a place where they didn’t happen automatically. The most natural setting where you’ll have re-borrows is if you have some mutable reference foo: &mut SomeType. If you now call a &mut self method from an impl SomeType, say some_method on foo, i.e.

struct SomeType;
impl SomeType {
    fn some_method(&mut self) {}
}
fn baz() {
    let foo: &mut SomeType = &mut SomeType;
    foo.some_method();
    foo.some_method();
}

why doesn’t the first call of some_method consume foo? The answer is: re-borrows!


Ah, and by-the-way, the whole &mut *… thing is just the syntax for re-borrowing. Don’t try to think about the dereferencing (the *) itself too much, it doesn’t have too much meaning. Technically, and I guess you know that, the idea is that if transaction: &mut Transaction then *transaction: Transaction, so the dereference removes the indirection. However this expression *transaction is a “place-expression”, you can’t do too many things with it, in particular you can’t move it unless Transaction: Copy. You can however create new references to it, that’s what &mut *transaction does. While that new reference is alive, the original reference transaction is considered “in use” or in other words “re-borrowed”.

I think the &'s mut &'l mut T to &'s mut T conversion is also something that the dereferencing operator can do, so I wouldn’t be surprised if *&mut transaction works, too.

2 Likes

Thank you for such a great explanation, makes sense now! *&mut transaction by the way results in "cannot move out of a mutable reference".

A nice way to think about &mut T is that it disallows interleaving access to T, but nested access is fine. @steffahn did an amazing job of explaining this in detail

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.