For simple accessors it's not a problem, but still an unfortunate thorn to deal with - especially when polymorphism of other types is generally dealt with very well in Rust.
Ultimately, any function utilising generic mutability would be an accessor of some sort (possibly with side effects) - but some are more complex than others. A contrived example:
fn get_some_values(data: &Foo, iter: &mut Iterator<Item=SomeIndexer>) -> Vec<&Val> {
let mut chosen_vals: Vec<&Val> = vec![];
for i in iter { // Some sequence of index values
let val = &data[i];
// Add first-chosen value to `chosen_vals`, then insert subsequent vals
// at a random place if some comparison between the currently-last val
// succeeds
if let Some(v) = data.last() {
if some_comparison(v, val) {
let insert_ind = rand(0, vals.len());
vals.insert(insert_ind, val);
}
} else {
vals.push(val);
}
}
chosen_vals
}
Now imagine you need to also be able to receieve mutable output, where data
is a &mut Foo
.
You could minimise the repeated code by instead returning the chosen indexes from iter
, instead of references directly. Then feed these index value into smaller getter functions:
fn get_some_values(data: &Foo, iter: &mut Iterator<Item=SomeIndexer>) -> Vec<&Val> {
get_some_indices(data, iter).into_iter().map(|i| &data[i]).collect()
}
fn get_some_values_mut(data: &mut Foo, iter: &mut Iterator<Item=SomeIndexer>) -> Vec<&mut Val> {
get_some_indices(data, iter).into_iter().map(|i| &mut data[i]).collect()
}
fn get_some_indices(data: &Foo, iter: &mut Iterator<Item=SomeIndexer>) -> Vec<Index> {
let mut chosen_indices: Vec<&Val> = vec![];
for i in iter {
let val = &data[i];
if let Some(v) = data.last() {
if some_comparison(v, val) {
let insert_ind = rand(0, vals.len());
vals.insert(insert_ind, val);
}
} else {
vals.push(i);
}
}
chosen_indices
}
The final solution then:
- Allocates an intermediate Vec
- Performs indexing of
data
twice (which might be expensive)
- Involves some non-trivial new getter functions
With mutability generics, this would have been a one-line change with no performance impact.
Good example. Yeah, I suppose traits can be used like this to achieve a kind of pseudo-overloading. @jonh's comment is correct however; my main reasoning behind this is to remove the need for the multitude of immut-then-mut traits and functions out there.