Question about nested references

Hi all,

I've a rather fundamental questions about Rust:

As we know every variable in Rust has an owner and you can borrow owned variables, but sometimes you can also find stuff like "&&T", "&&&T"... and so on. In other languages I would just call that nested references, but I'm currently not 100% sure what it semantically means in Rust and what it implies. Is eg "&&T" a borrowed value which gets borrowed again? Would this imply that also "the borrow itself" has an owner? When it's the case that "&&T" is a let's say "subborrow" of "&T" this should imply that &T always outlives &&T, right?

As you can maybe see I'm currently a litte bit confused about all that. :smiley:

Regards
keks

1 Like

They're regular loans of a thing that also happens to be another loan. &&T is &X where X = &T. In C terms it's a pointer to a pointer T**, but with borrow checking added.

Rust has a bit of special handling for reference-to-a-reference in the . operator (auto deref), but otherwise they're just a regular type.

3 Likes

Preliminaries: A generic T includes all &U. [1] References are full-fledged types. If you're in a generic setting and you have a T, and you create a &T, and T happens to be a &U... then yes, you have borrowed the &U and created a &&U. A Box<T> owns the T, so a Box<&U> owns the &U.


So yes, references can have owners. If you see a struct with a lifetime, there's a good chance it owns a reference. When we're purely talking about &&T and the like, however, it arguably doesn't matter a whole lot -- &T is Copy, so you can copy the &T out from behind the &&T and so on.

The lifetimes do have to make sense: You can't have a &'long &'short T where 'long is strictly longer than 'short. You can have a &'short &'long T where 'short is at most as long as 'long.


It gets more interesting and nuanced when you have &mut. Let's first consider a &mut &T: you can still copy the &T out, but you may actually want to mutate the original &T. You can make it reference something else.

See here for example. The &mut self is a &mut &[u8], and the &[u8] gets modified to point to some later position.

&mut T are also not Copy, so you can't get the inner &'long mut T out from a &'short mut &'long mut T unless you have a &'long mut T to replace it with. And you can't always move a &mut out of a struct that owns it, for example.


As for the borrowing terminology... I would go with "nested reference" or "nested borrow", etc. There's another feature of Rust called reborrowing which let's you get...

  • A &'short mut T from a &'long mut T (without moving the latter)
  • A &'short mut T from a &'short mut &'long mut T
    • But not a &'long mut T
  • A &'short T from a &'short &'long mut T
    • But not a &'short mut T much less a &'long mut T

...and those are more like subborrows in my opinion.

Reborrowing is a larger topic so I'll stop here.


  1. Modulo any trait bounds on the T. â†Šī¸Ž

8 Likes

Great, that clarifies my question! Thanks for the quick, precise answers, folks! :slight_smile:

In this regard, a reference is no different from other parametric types. You wouldn't think specially of Option<Option<T>> or Vec<Vec<T>>, for instance. This is the same with references. &&T is simply &(&T). Just like how you can put an optional into an optional, or a vector inside a vector, you can also refer to a reference. The outer reference would then point to an address where the resident value also happens to be another (inner) reference. There's no special meaning to this – it's simply a composition of types.

In addition, you can of course compose different types, that's the whole point of types being parametric/generic. You can make an &Option<T> or an Option<&T> or even a &Vec<Option<&T>> (note that the latter contains four layers of generic nesting). There's still nothing special about any one of these. The language doesn't know (or care) about every single one of the infinite number of combinations you can build up like this; there's no rule about &Vec<Option<&T>> specifically built into the compiler or the standard library. The ability to declare such types is entirely a mechanistic process, which is enabled by the simple core idea of generics/parametricity.

5 Likes

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.