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()
};
}
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).
(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.
Currently Rust doesn't support abstracting over mutability.
There are some ways this can be achieved, although they aren't ideal:
- Use a macro to expand the duplicate code, declare the macro and share between both functions - needs to be constructed so it works for mutable and immutable of course.
- Write the immutable version of the function (to ensure nothing is changed), then write a wrapper function for the mutable version which performs an
unsafecast on the result to make it mutable.
Neither of these are very appealing (a macro is overly verbose and a little less readable, adds some code-bloat), the unsafe is more readable, but it would be nice to avoid since casting from immutable to mutable isn't so nice to have through a code-base.
For now the best option as far as I can see (where copy-pasting code isn't acceptable), is to write an immutable version of the function, then wrap it with a mut version of the function where both inputs and outputs are mutable.
This requires an unsafe cast on the output of the function, so it's not ideal.
Note: it's important to have the immutable function contain the body of the code, since the reverse will allow accidental mutating of what might be immutable input.
来源:https://stackoverflow.com/questions/41436525/how-to-avoid-writing-duplicate-accessor-functions-for-mutable-and-immutable-refe