Is the borrowing to the field considered as the borrowing to the container?

Consider two contrast examples

struct A{
   i:i32,
   i2:f64
}
fn main(){
  let mut a = A{i:0,i2:0.0};
  let rf_field = &a.i;
  let rf = & mut a;
  println!("{rf_field}");
}

Fail to compile this example. In this example, the borrowing to the field seems to be considered as the borrowing to the container.

fn main(){
  let mut a = A{i:0,i2:0.0};
  let rf_field = & mut a.i;
  let rf_field2 = & mut a.i2;
  println!("{rf_field}");
}

Individual borrowing to each field does not result in failure even though they overlap. However, for the second case, if the conclusion gotten in the first example is true, why aren't the two borrowings to the fields considered as two borrowings to the container? What's the reason the first example is wrong but the second example is ok?

Rust allows you to borrow fields separately, but borrowing &mut a claims exclusive access to the whole struct -- overlapping the field borrow.

3 Likes

Another way to think about it is that you can't modify or observe a.i via rf_field2 and you can't modify or observe a.i2 through rf_field2. But you can modify and observe a.1 through both rf_field and rf. That is the type of data-race prone aliasing that Rust forbids.

(My "can't modify or observe" comments wouldn't hold up in the face of self-referential structs -- and that, in combination with field-based borrow splitting, is one of a number of reasons that self-referential structs cannot be trivially and soundly created in Rust.)
2 Likes

BTW, what is the mental model to think about the self-referential issue?

There are a few ways to think about why they are problematic. The aliasing of distinct fields is one. Another is that the language doesn't distinguish stack references from heap references, so you can't move a self-referencial struct (the references might dangle if you did). Another is you could never call a mutable method on one as the &mut self would kill the outstanding references, making the struct as a whole invalid. That in turn means you can't have a destructor.

I'll come back to this when at a computer, but you could try to create a self-referencial struct (using references) in safe code to see how it acts. It is possible, but very restrictive. Maybe it would be informative (or maybe more confusing or distracting).

1 Like

Ok, here's how to create some self-referential structs in safe Rust. It has very niche utility. As I mentioned, this may be more distracting than informative.


For a first take, it's actually quite easy to make a self-referential struct. The problem is, we can't do anything with it! Once you call point_at_self, you can't ever use the struct again. You can't even have a destructor, as that would be a later use.

What's going on? Here:

impl<'a> SelfRef<'a> {
    fn point_at_self(&'a mut self) {

We've used the anti-pattern of &'a mut Thing<'a>, wherein we exclusively borrow some Thing for the entirety of it's remaining valid scope. This is why we can no longer use the struct -- it's exclusively borrowed by something else "forever".

But the thing is, we had to do this in order to create the self-references: If we didn't borrow for 'a, we couldn't reborrow &mut self for long enough. This is another restriction around self-referencial structs: We need to modify our self-referencing fields while borrowing other parts of ourself simultaneously, and that leads to the anti-pattern. Remember that reborrows can't outlive the original borrow!


Can this be useful in any way, or is our data just useless? It's not useless -- it can still be accessed -- but it can only be accessed via the existing exclusive reference in some way. We can't return the &mut self directly, as that would invalidate all the references we just created.

But we can return some field. If we're using shared references, they could potentially even overlap.

It's clearly not to useful in this simple case, but you can build up trees of mutable data all referencing the same data store with enough effort. But since the underlying data becomes inaccessible forever, you'd almost always be better off storing it separately (in an owned struct with no lifetime) and building such a tree in a different data structure (with a lifetime).


I will highlight one pattern from the playground which makes self-referencial structs somewhat more palatable: the scoped or visitor-like pattern:

fn visit_self_ref<F, R>(f: F) -> R
where
    F: FnOnce(SelfRefData<'_>),
{
    let mut spidey = SelfRef::default();
    let data = spidey.point_at_self();
    f(data)
}

The idea is that the closure can't know or care about the limitations of your self-referencial struct, so if you can reasonably construct it on the fly, you can encapsulate the weirdness.

But it's still an extremely niche area of the language that I haven't personally found any use for outside of playing.

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.