Partial borrows are considered a very high difficulty problem to solve, mostly due to semver (API stability) concerns, and what the syntax should look like.
Due to this, there has been no progress on partial borrows at all, as far as I'm aware.
I think there is a way we could at least move forward and set up the basic compiler infrastructure for partial borrows, and let nightly users experiment with what kind of new patterns partial borrowing would allow, and how useful it is in general.
The idea is to bypass both the semver and syntax concerns, by introducing a perma-unstable rustc_partial
attribute that can be used on function arguments to instruct the borrow checker to split up the borrow into disjoint parts. This attribute can only be applied to function items that are pub(crate)
, or more restricted in terms of visibility. This would forbid partial borrows from "leaking" into the ecosystem, and make semver concerns much less of an issue.
In my experience, partial borrows are most useful for splitting up complex internal systems, so it would still be very useful, especially for experimentation purposes, even if it can't be used for public facing APIs.
How it would look like
Here is my basic idea of what it could look like. Subject to bikeshedding, but since it would be perma-unstable, I don't think there needs to be too much bikeshedding, as long as it's technically sound.
Let's start with an example (based on code in one of my projects)
impl App {
// Borrow the field `meta_state` immutably, and `hex_ui` mutably
pub(crate) fn switch_layout(#[rustc_partial(meta_state, mut hex_ui)] &mut self, k: LayoutKey) {
self.hex_ui.current_layout = k;
// Set focused view to the first available view in the layout
if let Some(view_key) = self.meta_state.meta.layouts[k]
.view_grid
.first()
.and_then(|row| row.first())
{
self.hex_ui.focused_view = Some(*view_key);
}
}
}
App::switch_layout
could then be used like this: (original code)
for (k, v) in &app.meta_state.meta.layouts {
if ui.selectable_label(win.selected == k, &v.name).clicked() {
win.selected = k;
app.switch_layout(k);
// roughly equivalent to this from a borrowing standpoint:
// App::switch_layout(&mut app.hex_ui, &app.meta_state.meta, k);
}
}
The basic syntax is #[rustc_partial(fields...)]
, where each field is the name of a field on the argument the attribute applies to, and an optional mut
specifier, to specify a field as being mutably borrowed (instead of the default of immutably borrowed).
Trying to use it on a pub fn
item would result in a compile error:
pub fn foo(#[rustc_partial(mut field)] &mut self)
// ^ Error: `rustc_partial` cannot be used on `pub` items due to API stability concerns.
This would be a perma-unstable attribute that's only available under a feature flag, and could be removed once a better solution for partial borrows is devised, or if it's decided that partial borrows are not a feature that Rust should have.
While it might seem undesirable to have a perma-unstable feature, it would provide value over not having anything until we have a final design:
- Implementing it will set up the basic compiler infrastructure for experimenting with different solutions to partial borrows.
- Application and game developers who don't mind using nightly can use it to alleviate a lot of borrow checking based woes, and can focus on solving problems instead of constantly restructuring their code just to appease the borrow checker.
There is also precedent for unstable features existing which don't have a clear final design, like specialization, which among other things has soundness issues, but is still useful to have.