Projections that do not decide mutability up front are possible
⚓ Rust 📅 2025-07-13 👤 surdeus 👁️ 17I encountered the following problem while working on a transpiler from non-Rust code to Rust. Suppose that I have these structs:
#[derive(Debug, bytemuck::TransparentWrapper)]
#[repr(transparent)]
struct Foo(pub i32);
struct Bar {
pub x: i32,
pub y: i32,
}
Suppose that I want to pretend that Bar has fields of type Foo as much as it has fields of type i32, that is, be able to write an expression that:
- is a place expression,
- denotes the field
xoryof aBar, in the same way thatbar.xandbar.ydo, - has the type
Foo, noti32(which is sound because of therepr(transparent)), and - can be borrowed as mutable or shared without deciding while writing the expression.
After presuming for a while that this is impossible (after all, conventional Rust APIs have all these separate *_ref() and *_mut() functions, which must be there for a reason), I realized today that there is in fact an overloadable operator in Rust that offers this opportunity: the indexing operator.
use std::ops;
use bytemuck::TransparentWrapper;
struct X;
struct Y;
impl ops::Index<X> for Bar {
type Output = Foo;
fn index(&self, X: X) -> &Self::Output {
TransparentWrapper::wrap_ref(&self.x)
}
}
impl ops::Index<Y> for Bar {
type Output = Foo;
fn index(&self, Y: Y) -> &Self::Output {
TransparentWrapper::wrap_ref(&self.y)
}
}
impl ops::IndexMut<X> for Bar {
fn index_mut(&mut self, X: X) -> &mut Self::Output {
TransparentWrapper::wrap_mut(&mut self.x)
}
}
impl ops::IndexMut<Y> for Bar {
fn index_mut(&mut self, Y: Y) -> &mut Self::Output {
TransparentWrapper::wrap_mut(&mut self.y)
}
}
These definitions let this sort of code compile:
fn simple_example() {
let mut bar = Bar { x: 0, y: 0 };
bar[X] = Foo(1);
bar[Y] = Foo(2);
println!("{:?} {:?}", bar[X], bar[Y]);
}
fn more_specific_borrow_checked_usage(input: &mut Bar) {
// Can have multiple borrows (must not use `&mut input`)
let x1 = &input[X];
let x2 = &input[X];
println!("{x1:?}, {x2:?}");
// Can mutate (must not use `&input`)
let x3 = &mut input[X];
*x3 = Foo(10);
assert_eq!(input.x, 10);
}
It will even work on foreign types:
impl ops::Index<X> for [i32; 2] {
type Output = Foo;
fn index(&self, X: X) -> &Self::Output {
TransparentWrapper::wrap_ref(&self[0])
}
}
// ...
I haven't yet used this trick (I only just thought of it while I was cleaning out some old draft posts) and I may or may not actually use it, but I thought it was worth sharing.
2 posts - 1 participant
🏷️ rust_feed