[Idea] A third kind of reference between "mutable" and "immutable" in Rust

This reminds me somewhat of the “unique immutable borrow” that comes up as a mechanism in closure captures, as described in the reference here, and then further here.

Oh… now I see you do already mention that later in the text. Arguably, there may be more value in keeping the already known term “unique immutable borrow” for such an exposition, and discuss a re-name separately.

This transitivity of course ends as soon as the contents of various UnsafeCell-base types are involved (e.g. Mutex, Cell, RefCell).

This section is getting quite confusing. Since when are we talking about something written “in T” or “mut T”, which you call “types” here, and what issues with Deref and Index? Oh, is this supposed to read as a sort of high-level overview like in a research paper? Scrolling ahead, I do find something about a “DerefIn” and “IndexIn” trait. At this point I’m not only confused as a reader, I’m also getting the impression you’re discussing much more here than “just” a third kind of reference type. Yet, I still haven’t found the motivating section.

When is &in T useful enough? When is it impossible to “just use &mut T instead” and live with the fact that a variable is marked “mut” even though for direct access (instead of indirectly through some reference) you could’ve left out that mut marker?


So I guess I’ll have to skip this and going to

though on the way I seem to be spotting in your table the mention of

  • 1 new reference type
  • 1 new raw pointer type
  • 2 new types for “(variable allocation pointer?)”

And I think many readers could appreciate if you considered learning from the RFC structure – even if this isn’t meant to be an actual proposal – in so far as to split off additional things that aren’t core to the idea, fully into a subsequent section akin to the “Future possibilities” in RFCs.


Okay, now I have actually read the “motivation” section, and I can’t say I’m convinced. It explains, as expected, that you can use &in T to avoid marking certain variables as mut. I still don’t really see what additional benefit this gives though.

To me, there seem to be much more useful extensions of “references between mutable and immutable”, in particular partial borrows (only access to certain fields of a struct). The benefit of those is that mutable partial borrows of disjoint sets of fields can co-exist. On the other hand, a &in T reference in your discussion here can not coexist with a &T (AFAICT), so on an abstract type T (e.g. a struct without public fields) it’s basically exactly as restrictive as &mut T. Yes, you don’t need to write mut to create it, but with a real &in T implementation, one would presumably also just be allowed to create a Cell-like type that can do a fn (&in InCell<T>) -> &mut T projection, and any struct implementor that wants to allow users to drop the mut on the variable could wrap their fields in this cell-like type.

A more complex version of “partial borrows” may include e.g. mutable access to some fields, immutable access to other fields, no access at all to yet-other fields. In consideration of such an idea, your &in T becomes like a very particular version of “partial borrows” (immutable access to immediately contained values, like the address and metadata of references, but mutable access to the targets of contained mutable references). But I feel like this is one of the less commonly useful choices that one may actually care about for partial borrows, so it seems unhelpful to single out as the best choice for single kind of “third kind of reference” to introduce.[1]

I guess – viewing it as a sort of partial borrow, and hence coming up with my own motivational arguments of what your proposal might even allow for besides the almost kind-of superficial question of “which variables are marked mut?”, I suppose something like

let mut x = 1;
let s = (&mut x, 2);

let r = &s.1;

some_function(&in s);

println!("{r}");

could probably be allowed then, whereas some_function(&mut s) would kill the immutable borrow of s.1.

That’s actually interesting… currently this is apparently not allowed for the real, existing unique immutable references, as this fails to compile:

let mut x = 1;
let s = (&mut x, 2);

let r = &s.1;

(|| {
    println!("{s:?}");
    *s.0 += 10;
})();

println!("{r}");

Of course, even this minor benefit already runs into limitations again if you argue it for explicit &in T types: The access to &s.1 can only be allowed because it’s direct access to a public field which in turn doesn’t contain any &mut T-like data. But if s was some abstract struct with private fields, and/or 2 wasn’t a i32 but some complex type that might contain – say – a &'static mut T value – or perhaps the abovementioned InCell<T> type, then you can’t actually keep even an immutable reference to this field anymore; so we also rely on “implementation details” of i32.

To solve both these issues, you might actually also want to introduce a complementary partial borrow, let’s call it “pre-immutable” access, and write – for the sake of keeping with the spirit of your existing notational mnemonics – its syntax as &as T (with as for “allow shallow reads” or “another arbitrarily stolen syntax”).

This would be a shared borrow with immutable access to everything but the things that &in T has mutable access to, so by design &as T can co-exist with &in T.

// some_function(_: &in S<'_>) defined elsewhere

struct Field2(i32);
struct S<'a>(&'a mut i32, Field2);

impl<'a> S<'a> {
    fn new(r: &'a i32, x: i32) -> Self { Self(r, Field2(x) }

    fn print_field2(&as self) {
        let value: i32 = self.1.0;
        println!("{value}");
    }
}

// below code no longer needs to rely on any implementation details
// of `S` or `Field2`

let mut x = 1;
let s = S::new(&mut x, 2);

let r = &as s;

some_function(&in s);

print_field(r);

but arguably, there’s not really any good place to stop with introducing new kinds of reference types, right?

It’s already sufficiently tedious that so much API in Rust needs to be duplicated between &T and &mut T versions of the same thing, I don’t think this should ever be made worse.

I do acknowledge that some of my arguments here are addressing this as a feature proposal.


Also I should probably read the rest of your post now, I still haven’t gotten further than somewhere in the middle of “Pre-mutable place expressions”. I’ll end this first reply here though to keep it a bit shorter, and might edit this later or write a second reply if I find anything in the remaining part that I want to comment on.


  1. I’d personally much rather see if partial borrows could somehow – at least conceptually – unify &T and &mut T, so that the information of “which fields are accessible” is just a sort of generic parameter of the reference type, and the existing information of “mutable or not” can be part of such a parameter. ↩︎

1 Like