Question:I am designing some struct with a prepare-commit pattern in rust, which mostly looks like this after simplified:
dataas mutable, upon the creation of
Adderstruct, all other immutable borrows (e.g.
Holderhere) could not live past. However, the
add_oncecalls really do not need to borrow
commitdoes that. Ideally there should be some way in rust to let the compiler know that
addedas mutable but not
data, so that
Holdercould live past
add_oncecalls but not
I do know two possible current solutions to this, however I think they both have drawbacks:
- I could wrap fields of
RefCelland borrow entire
Adderimmutably, only borrows mutably when needed. The problem with this approach is that
RefCellchecks borrowing dynamically, when in this case it really does not need to be that way since all lifetime bounds should be known at compile time, just different parts of a struct could have different bounds.
- I could do
unsafeblocks to convert references to raw pointers and do whatever I want. However as far as I think about it I could not find a way to do it without disable all borrow checks altogether (I don’t really know how to do unsafe rust properly). And I want a borrow check so that if
committhe compiler would still give a proper borrow error
So summary of questions:
- Is there a static and safe way in rust to borrow different parts of self, with different mutability?
- If not, is there a unsafe boilerplate to do so, still ensuring the proper borrow check as described above? (and if there is an existing crate for this)
- What is the best practice in general for these situations?
- Is there any RFC addressing this issue from rust language level?
Is there a static and safe way in rust to borrow different parts of self, with different mutability?
If not, is there a unsafe boilerplate to do so, still ensuring the proper borrow check as described above? (and if there is an existing crate for this)
You can use
unsafefor that, but it will require you to work with raw pointers only. You may be able to encapsulate it in a safe API, but it’s unlikely that inside the code you’ll have proper automatic verification. For this reason I also don’t believe there is a crate for that, at least I don’t know one.
What is the best practice in general for these situations?
I would use
RefCell(or other interior mutability primitives such as
Cell– note that if you can use
Cellit is actually zero-cost) for that generally, and use unsafe code if profiling shows a bottleneck. However, this kind of issues many times arises from improper API design. So first I’ll see if I can redesign my API so the problem will not exist.
Is there any RFC addressing this issue from rust language level?
Not today, and I think it is very unlikely in the future.
There was something similar suggested while working on two phase borrows: https://github.com/rust-lang/rust/issues/49434. The idea was that any mutable reference will start as shared and only “upgrade” when it is actually used. In can be extended to “upgrade” only when a mutable reference is actually required, and this is basically what you propose.
However, the effect of two-phase borrows is completely local. For your code to work, we will have to need some way to represent time-aware mutable reference in a type. I don’t see that happening.
In addition, two-phase borrows work poorly with Stacked Borrows. @RalfJung ended up with just using raw pointers when two phase borrows are involved. Using them for everything will probably mean poor optimization opportunities (although maybe future models, or even Stacked Borrows itself, will find a way to handle that).
If you have better answer, please add a comment about this, thank you!