Hmm... so the problem is that when we overload []
with index_mut()
we only have the array, self, and the index to work with. We don't have a reference to what is actually about to be written at that index so we cannot enforce the invariant at write time.
However, we do have visibility into what is already stored in the element we are about to read or overwrite.
So how about we check the invariant holds for existing elements in the array instead, when we read or are about to write to an index?
use std::ops::{Index,IndexMut};
// Indexing the Numbers array will panic if an odd index contains a Even item
// and vice versa.
#[derive(Debug, PartialEq)]
enum Number {
Even(i32),
Odd(f32),
}
struct Numbers {
pub even: Vec<Number>,
pub odd: Vec<Number>,
}
impl Index<usize> for Numbers {
type Output = Number;
fn index(&self, index: usize) -> &Self::Output {
let i = index / 2;
if (index % 2) == 0 {
// Expect Even in element
match self.even[i] {
Number::Odd(_) => {
panic!("Error: There is an odd thing at an even index.");
},
_ => (),
}
&self.even[i]
} else {
// Expect Odd in element
match self.odd[i] {
Number::Even(_) => {
panic!("Error: There is an even thing at an odd index.");
},
_ => (),
}
&self.odd[i]
}
}
}
impl IndexMut<usize> for Numbers {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
println!("IndexMut {:?}", index);
let i = index / 2;
if (index % 2) == 0 {
// Expect Even in element
match self.even[i] {
Number::Odd(_) => {
panic!("Error: There is an odd thing at an even index.");
},
_ => (),
}
&mut self.even[i]
} else {
// Expect Odd in element
match self.odd[i] {
Number::Even(_) => {
panic!("Error: There is an even thing at an odd index.");
},
_ => (),
}
&mut self.odd[i]
}
}
}
fn main () {
let mut numbers = Numbers {
odd: Vec::new(),
even: Vec::new(),
};
numbers.even.push(Number::Even(0));
numbers.odd.push(Number::Odd(10.0));
numbers.even.push(Number::Even(20));
numbers.odd.push(Number::Odd(30.0));
// Indexing now returns different types for odds and evens
println!("{:?}", numbers[0]);
println!("{:?}", numbers[1]);
println!("{:?}", numbers[2]);
println!("{:?}", numbers[3]);
// Cause a panic with Even item in odd index.
numbers[3] = Number::Even(20);
println!("{:?}", numbers[3]);
}
Quite likely not what you want as the error does not show up until long after the erroneous write is made. Which might make debugging harder.
On the other hand the complete program will never see the wrong thing in the wrong place. It either runs OK or dies.
Anyway, I thought this was a weird and fun thing to do and have learned some more corners of Rust in the process.
In the play ground : Rust Playground