Files
Sprimo/skills/unsafe-checker/rules/safety-07-unsafe-pair.md
2026-02-12 22:58:33 +08:00

108 lines
2.9 KiB
Markdown

---
id: safety-07
original_id: P.UNS.SAS.07
level: P
impact: MEDIUM
---
# Provide Unsafe Counterparts for Performance Alongside Safe Methods
## Summary
When providing performance-critical operations that skip safety checks, offer both a safe checked version and an unsafe unchecked version.
## Rationale
Users who need maximum performance can opt into unsafe, while others get safety by default. This follows the "safe by default, unsafe opt-in" principle.
## Bad Example
```rust
// DON'T: Only provide unsafe version
impl<T> MySlice<T> {
/// Gets an element by index.
///
/// # Safety
/// Index must be in bounds.
pub unsafe fn get(&self, index: usize) -> &T {
&*self.ptr.add(index)
}
}
// DON'T: Only provide checked version when performance matters
impl<T> MySlice<T> {
pub fn get(&self, index: usize) -> Option<&T> {
if index < self.len {
Some(unsafe { &*self.ptr.add(index) })
} else {
None
}
}
// Missing: get_unchecked for performance-critical code
}
```
## Good Example
```rust
// DO: Provide both versions
impl<T> MySlice<T> {
/// Gets an element by index, returning `None` if out of bounds.
#[inline]
pub fn get(&self, index: usize) -> Option<&T> {
if index < self.len {
// SAFETY: We just verified index < len
Some(unsafe { self.get_unchecked(index) })
} else {
None
}
}
/// Gets an element by index without bounds checking.
///
/// # Safety
///
/// Calling this method with an out-of-bounds index is undefined behavior.
#[inline]
pub unsafe fn get_unchecked(&self, index: usize) -> &T {
debug_assert!(index < self.len, "index out of bounds");
&*self.ptr.add(index)
}
/// Gets an element, panicking if out of bounds.
#[inline]
pub fn get_or_panic(&self, index: usize) -> &T {
assert!(index < self.len, "index {} out of bounds for len {}", index, self.len);
// SAFETY: We just asserted index < len
unsafe { self.get_unchecked(index) }
}
}
```
## Standard Library Patterns
| Safe Method | Unsafe Counterpart |
|-------------|-------------------|
| `slice.get(i)` | `slice.get_unchecked(i)` |
| `str.chars().nth(i)` | `str.get_unchecked(range)` |
| `vec.pop()` | `vec.set_len()` + `ptr::read` |
| `String::from_utf8()` | `String::from_utf8_unchecked()` |
## Naming Conventions
- Safe: `method_name()`
- Unsafe: `method_name_unchecked()`
- Or: `get()` vs `get_unchecked()`
## Checklist
- [ ] Does my safe method have an unsafe counterpart for hot paths?
- [ ] Does my unsafe method have a safe alternative for normal use?
- [ ] Are both methods documented with their trade-offs?
- [ ] Does the unsafe version include debug assertions?
## Related Rules
- `general-02`: Don't blindly use unsafe for performance
- `safety-09`: Add SAFETY comments