Because it requires the precondition that the index is in-bounds in the slice. That precondition would be defined in the trait docs; any code that calls the trait method must use an unsafe
block to promise that it meets the condition. (I should've explicitly written that documentation down when I wrote the example in the first place; my bad.)
EDIT: Here's a revised trait signature, borrowing the method documentation wording from std
's get_unchecked functions:
/// A trait for setting the value of a slice at a particular index.
///
/// (But if implementations don't actually do that, that's just a logic error.
/// Consumers of this trait aren't allowed to rely on that for memory safety.)
pub trait SliceSetIndex<T> {
/// Calling this method with an out-of-bounds index is *undefined behavior*.
unsafe fn set_unchecked(self, slice: &[T], value: T);
}
So, suppose your impl looks like this:
pub struct EvenOnly;
impl<T> SliceSetIndex<T> for EvenOnly {
fn set_unchecked(self, slice: &[T], value: T) {
if !self.is_even() {
unsafe { immediate_ub(); }
}
}
}
As you say, this doesn't violate a trait contract because the trait does not impose any requirement on the impl. But it doesn't need to. This code violates the unsafe
rules in general – you've written an unsafe
block to use an unsafe operation where you cannot guarantee that the operation is not UB.
On the other hand, in my original impl
impl<T, U: SliceIndex<T>> SliceSetIndex<T> for U {
unsafe fn set_unchecked(self, slice: &[T], value: T) {
unsafe { *slice.get_unchecked(self) = value; }
}
}
This impl is permitted. It relies on (1) the promise by the caller, arising from the SliceSetIndex::set_unchecked
precondition, that self
is in-bounds in the slice, and (2) the promise from std::slice::SliceIndex
that if self
is in-bounds in the slice, then the returned reference is valid. Because of those 2 promises, this impl can guarantee that it does not violate memory safety (violations can only arise from the caller violating the SliceSetIndex::set_unchecked
precondition).
Like any trait impl, it is permitted to relax the precondition from the trait, and not permitted to tighten the precondition from the trait; the only difference here is that the precondition is defined by the documentation rather than the type system. Requiring the index to be even would be a tightening of the precondition, which is not permitted.
Edited to add:
The meaning of unsafe trait
is that the trait promises that consumers of the trait can rely on some additional guarantee, beyond the normal guarantees of "methods are safe to call when their preconditions are met". std::slice::SliceIndex
and TrustedLen
both have such additional guarantees – consumers of TrustedLen are permitted to write unsafe code that relies on the len being accurate, and consumers of SliceIndex are permitted to rely on get_unchecked
not only being safe to call, but returning a safe reference. (Note that since get_unchecked
returns *const Output
rather than &Output
, implementations would be permitted to return an invalid pointer if SliceIndex didn't impose additional restrictions on them by being an unsafe trait
). With my SliceSetIndex
trait, a consumer of the trait can't rely on any additional behavior other than the methods being safe to call.