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).