[Idea] Mut/immut markers for references

I'd like to suggest a language feature, mutable/immutable reference markers, they'd look like lifetimes on references, but will tell the compiler to infer mutability of the reference example:

struct MyArray([u32; 10]);
impl MyArray {
  fn get<'m: ?mut>(&'m self, index: usize) -> &'m u32 {
    &'m self.0[index]
  }
}

and if that get() gets called in the code, compiler needs to infer what minimal type of reference that &'m u32 should be, so if it's called like println!("{}", my_array.get(5)), it will be immut, or *my_array.get(6) = 4; will be mut.

2 Likes

I'm not sure what the state of the art is on this, but searching up "keyword generics" and "effects" should yield some of the ongoing efforts towards this.

3 Likes

A recent thread (with links to other discussions): Add mut-abstractions

1 Like

If I understand your intent correctly, it's to abstract over shared and mutable references.

If so, then this idea will first appear to work at the surface level, but then you'll get borrowck errors if the fn/method does things that would be allowed with a shared borrow but not with an exclusive one e.g. copying the borrow 2 times.

That is to say, it wouldn't actually abstract over borrow mutability, because you can't, in general, write fns in current-day Rust that would compile regardless of the mutability of the borrows it uses.

Indeed, in current-day Rust, you can't. I would assume that's why this was posted as an idea here so that maybe in some future Rust it could be possible. It's quite common for implementations of Deref{Mut} or Index{Mut} and such to have essentially the same code repeated twice, and something like this would reduce such boilerplate.

A function like that would just be checked using the least common factor; you can reborrow it with the same rules as a &mut, and read from it like you can with &, but can't share it or write through it (except with interior mutability). That's effectively an "exclusive readonly reference", which would have some uses on its own as well.

A generic reference type could allow other things too; a reference that you can't access at all would be fine for a lot of functions that are internally just pointer offsetting, like slice::split_at. The generic signature would just communicate that the outputs get whatever permissions the input has.

Of course there are a lot of details that would need to be figured out, and this kind of change could cause large amounts of churn, which might make it infeasible.

This is true. But it is equally true that such impls are generally 1-liners w.r.t. the length of the body of the trait method being implemented, i.e. it's also not that big of a deal.

If like to split out a couple of things. Rust already has generic borrows i.e. &mut T and &T. Therefore to keep things from turning confusing, this feature needs some other name e.g.mutability generics (though I'm sure there will be loads of bikeshedding :slight_smile: ). Accordingly the example you name, even if it didn't already exist, could be implemented in Rust no problem. The borrow mutability generics part would be new (i.e. no need for separate split_at and split_at_mut methods), but this feature, as stated, would be a lot more limited in practice than a name like "mutable generics" or "mut/immut markers for references" would imply due to the issue I mentioned earlier.

So the issue is that while it would allow some stuff, wouldn't really be pulling its weight without the added flexibility of really being able to abstract over mutable and shared borrows IMO,and would instead be a niche feature at best.

That seems like the correct behavior for such an abstraction: if you're abstracting over & vs &mut, then you have to only allow behavior that would be allowed by both. That should mostly work as expected.