Consider the following situation.
Person A designs a marker trait for an iterator that always returns slices of the same length:
/// Iterates over slices of the same length
trait SameSliceIter<'a, T: 'a>: Iterator<Item = &'a [T]> {}
Person B uses the guarantee provided by this trait to write some unsafe (possibly unnecessarly unsafe, but nevertheless sound based on the guarantee) code:
fn sum_last<'a, T>(mut iter: impl SameSliceIter<'a, T>) -> Option<T>
where
T: Copy + Add<Output = T> + 'a,
{
let first_slice = iter.next()?;
let last_index = first_slice.len().checked_sub(1)?;
let sum = unsafe {
iter.fold(*first_slice.get_unchecked(last_index), |sum, t| {
sum + *t.get_unchecked(last_index)
})
};
Some(sum)
}
Person B makes an erroneous but safe implementation of this trait:
struct Yolo<'a, T>(&'a [T]);
impl<'a, T> Iterator for Yolo<'a, T> {
type Item = &'a [T];
fn next(&mut self) -> Option<Self::Item> {
use rand::{thread_rng, Rng};
let mut rng = thread_rng();
let len = rng.gen_range(0..(self.0.len()));
Some(&self.0[0..len])
}
}
impl<'a, T> SameSliceIter<'a, T> for Yolo<'a, T> {}
As the result using safe erroneous code with unsafe non-erroneous code results in undefined behavior:
fn main() {
let vec = vec![0; 1 << 20];
let yolo = Yolo(&vec);
dbg!(sum_last(yolo));
}
unsafe precondition(s) violated: slice::get_unchecked requires that the index is within the slice
Now the question is: what went wrong?
I would guess in this particular situation either the trait SameSliceIter
should have been made unsafe
, or the person B should not have trusted all trait implementations to be correct. In which case it follows: unsafe code should not trust trait invariants, unless the trait is unsafe. Going further, which invariants should unsafe code trust? E.g. shoud one use (
time::Date.day()
- 1)
as an unchecked index in [&str; 31]
?
It raises a bigger problem.
Unsafe code is a subject of greater scrutiny than safe code and requires significantly more effort to write. If unsafe code relies on an invariant guaranteed by safe code, then it may transition the responsibility for correctness to the safe code, which does not necessarily meet the quality standards unsafe code ideally should. This makes safety of unsafe code depend not only on the correctness the code itself, but also of every piece of safe code it relies upon.