How to avoid writing duplicate accessor functions for mutable and immutable references in Rust?
Solution 1:
(playground links to solutions using type parameters and associated types)
In this case &T
and &mut T
are just two different types. Code that is generic over different types (at both compile-time and run-time) is idiomatically written in Rust using traits. For example, given:
struct Foo { value: i32 }
struct Bar { foo: Foo }
suppose we want to provide Bar
with a generic accessor for its Foo
data member. The accessor should work on both &Bar
and &mut Bar
appropriately returning &Foo
or &mut Foo
. So we write a trait FooGetter
trait FooGetter {
type Output;
fn get(self) -> Self::Output;
}
whose job is to be generic over the particular type of Bar
we have. Its Output
type will depend on Bar
since we want get
to sometimes return &Foo
and sometimes &mut Foo
. Note also that it consumes self
of type Self
. Since we want get
to be generic over &Bar
and &mut Bar
we need to implement FooGetter
for both, so that Self
has the appropriate types:
// FooGetter::Self == &Bar
impl<'a> FooGetter for &'a Bar {
type Output = &'a Foo;
fn get(self) -> Self::Output { & self.foo }
}
// FooGetter::Self == &mut Bar
impl<'a> FooGetter for &'a mut Bar {
type Output = &'a mut Foo;
fn get(mut self) -> Self::Output { &mut self.foo }
}
Now we can easily use .get()
in generic code to obtain &
or &mut
references to Foo
from a &Bar
or a &mut Bar
(by just requiring T: FooGetter
). For example:
// exemplary generic function:
fn foo<T: FooGetter>(t: T) -> <T as FooGetter>::Output {
t.get()
}
fn main() {
let x = Bar { foo: Foo {value: 2} };
let mut y = Bar { foo: Foo {value: 2} };
foo(&mut y).value = 3;
println!("{} {}\n", foo(&x).value, foo(&mut y).value);
}
Note that you can also implement FooGetter
for Bar
, so that get
is generic over &T
,&mut T
, and T
itself (by moving it in). This is actually how the .iter()
method is implemented in the standard library, and why it always does "the right thing" independently of the reference-ness of the argument its invoked on.
Solution 2:
You don't, really. Recall that T
, &T
and &mut T
are all different types. In that context, your question is the same as asking "How to avoid writing duplicate accessor functions for String
and HashMap
".
Matthieu M had the right terms "abstract over the mutability":
- Parameterisation over mutability
- Dealing with &/&mut in data structures: abstract over mutability or split types?
- A safe way to reuse the same code for immutable and mutable variants of a function?
- Abstracting over mutability in Rust
- "Mutability polymorphism"
- etc. etc. etc.
The TL;DR is that Rust would likely need to be enhanced with new features to support this. Since no one has succeeded, no one is 100% sure which features those would need to be. The current best guess is higher kinded types (HKT).