A few times, I've run into the scenario where an accessor method is needed for both mutable and immutable references.
For ~3 lines it isn't a problem to duplicate the logic, but when the logic gets more complex, it's not nice to copy-paste large blocks of code.
I'd like to be able to re-use the code for both.
Does Rust provide some way handle this better then copy-pasting code, or using unsafe
casts?
e.g.:
impl MyStruct {
pub fn get_foo(&self) -> &Bar {
// ~20 lines of code
// --- snip ---
return bar;
}
pub fn get_foo_mut(&mut self) -> &mut Bar {
// ~20 lines of code
// (exactly matching previous code except `bar` is mutable)
// --- snip ---
return bar;
}
}
Here is a more detailed excerpt of a code-base where an immutable return argument was cast to mutable to support both immutable and mutable versions of a function. This uses a wrapped pointer type (ConstP
and MutP
for immutable and mutable references), but the logic of the function should be clear.
pub fn face_vert_share_loop<V, F>(f: F, v: V) -> LoopConstP
where V: Into<VertConstP>,
F: Into<FaceConstP>
{
into_expand!(f, v);
let l_first = f.l_first.as_const();
let mut l_iter = l_first;
loop {
if l_iter.v == v {
return l_iter;
}
l_iter = l_iter.next.as_const();
if l_iter == l_first {
break;
}
}
return null_const();
}
pub fn face_vert_share_loop_mut(f: FaceMutP, v: VertMutP) -> LoopMutP {
let l = face_vert_share_loop(f, v);
return unsafe {
// Evil! but what are the alternatives?
// Perform an unsafe `const` to `mut` cast :(
// While in general this should be avoided,
// its 'OK' in this case since input is also mutable.
l.as_mut()
};
}
(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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With