# Safe Abstraction Examples Examples of building safe APIs on top of unsafe code. ## Example 1: Simple Wrapper with Bounds Check ```rust /// A slice wrapper that provides unchecked access internally /// but safe access externally. pub struct SafeSlice<'a, T> { ptr: *const T, len: usize, _marker: std::marker::PhantomData<&'a T>, } impl<'a, T> SafeSlice<'a, T> { /// Creates a SafeSlice from a regular slice. pub fn new(slice: &'a [T]) -> Self { Self { ptr: slice.as_ptr(), len: slice.len(), _marker: std::marker::PhantomData, } } /// Safe get - returns Option. pub fn get(&self, index: usize) -> Option<&T> { if index < self.len { // SAFETY: We just verified index < len Some(unsafe { &*self.ptr.add(index) }) } else { None } } /// Unsafe get - caller must ensure bounds. /// /// # Safety /// `index` must be less than `self.len()`. pub unsafe fn get_unchecked(&self, index: usize) -> &T { debug_assert!(index < self.len); &*self.ptr.add(index) } pub fn len(&self) -> usize { self.len } } ``` ## Example 2: Resource Wrapper with Drop ```rust use std::ptr::NonNull; /// Safe wrapper around a C-allocated buffer. pub struct CBuffer { ptr: NonNull, len: usize, } extern "C" { fn c_alloc(size: usize) -> *mut u8; fn c_free(ptr: *mut u8); } impl CBuffer { /// Creates a new buffer. Returns None if allocation fails. pub fn new(size: usize) -> Option { let ptr = unsafe { c_alloc(size) }; NonNull::new(ptr).map(|ptr| Self { ptr, len: size }) } /// Returns a slice view of the buffer. pub fn as_slice(&self) -> &[u8] { // SAFETY: ptr is valid for len bytes (from c_alloc contract) unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) } } /// Returns a mutable slice view. pub fn as_mut_slice(&mut self) -> &mut [u8] { // SAFETY: We have &mut self, so exclusive access unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) } } } impl Drop for CBuffer { fn drop(&mut self) { // SAFETY: ptr was allocated by c_alloc and not yet freed unsafe { c_free(self.ptr.as_ptr()); } } } // Prevent double-free impl !Clone for CBuffer {} // Safe to send between threads (assuming c_alloc is thread-safe) unsafe impl Send for CBuffer {} ``` ## Example 3: Interior Mutability with UnsafeCell ```rust use std::cell::UnsafeCell; use std::sync::atomic::{AtomicBool, Ordering}; /// A simple spinlock demonstrating safe abstraction over UnsafeCell. pub struct SpinLock { locked: AtomicBool, data: UnsafeCell, } pub struct SpinLockGuard<'a, T> { lock: &'a SpinLock, } impl SpinLock { pub const fn new(data: T) -> Self { Self { locked: AtomicBool::new(false), data: UnsafeCell::new(data), } } pub fn lock(&self) -> SpinLockGuard<'_, T> { // Spin until we acquire the lock while self.locked.compare_exchange_weak( false, true, Ordering::Acquire, Ordering::Relaxed, ).is_err() { std::hint::spin_loop(); } SpinLockGuard { lock: self } } } impl std::ops::Deref for SpinLockGuard<'_, T> { type Target = T; fn deref(&self) -> &T { // SAFETY: We hold the lock, so we have exclusive access unsafe { &*self.lock.data.get() } } } impl std::ops::DerefMut for SpinLockGuard<'_, T> { fn deref_mut(&mut self) -> &mut T { // SAFETY: We hold the lock, so we have exclusive access unsafe { &mut *self.lock.data.get() } } } impl Drop for SpinLockGuard<'_, T> { fn drop(&mut self) { self.lock.locked.store(false, Ordering::Release); } } // SAFETY: The lock ensures only one thread accesses data at a time unsafe impl Sync for SpinLock {} unsafe impl Send for SpinLock {} ``` ## Example 4: Iterator with Lifetime Tracking ```rust use std::marker::PhantomData; /// An iterator over raw pointer range with proper lifetime tracking. pub struct PtrIter<'a, T> { current: *const T, end: *const T, _marker: PhantomData<&'a T>, } impl<'a, T> PtrIter<'a, T> { /// Creates an iterator from a slice. pub fn new(slice: &'a [T]) -> Self { let ptr = slice.as_ptr(); Self { current: ptr, // SAFETY: Adding len to slice pointer is always valid end: unsafe { ptr.add(slice.len()) }, _marker: PhantomData, } } } impl<'a, T> Iterator for PtrIter<'a, T> { type Item = &'a T; fn next(&mut self) -> Option { if self.current == self.end { None } else { // SAFETY: // - current < end (checked above) // - PhantomData<&'a T> ensures the data lives for 'a let item = unsafe { &*self.current }; self.current = unsafe { self.current.add(1) }; Some(item) } } } ``` ## Example 5: Builder Pattern with Delayed Initialization ```rust use std::mem::MaybeUninit; /// A builder that collects exactly N items, then produces an array. pub struct ArrayBuilder { data: [MaybeUninit; N], count: usize, } impl ArrayBuilder { pub fn new() -> Self { Self { // SAFETY: MaybeUninit doesn't require initialization data: unsafe { MaybeUninit::uninit().assume_init() }, count: 0, } } pub fn push(&mut self, value: T) -> Result<(), T> { if self.count >= N { return Err(value); } self.data[self.count].write(value); self.count += 1; Ok(()) } pub fn build(self) -> Option<[T; N]> { if self.count != N { return None; } // SAFETY: All N elements have been initialized let result = unsafe { // Prevent drop of self.data (we're moving out) let data = std::ptr::read(&self.data); std::mem::forget(self); // Transmute MaybeUninit array to initialized array std::mem::transmute_copy::<[MaybeUninit; N], [T; N]>(&data) }; Some(result) } } impl Drop for ArrayBuilder { fn drop(&mut self) { // Drop only initialized elements for i in 0..self.count { // SAFETY: Elements 0..count are initialized unsafe { self.data[i].assume_init_drop(); } } } } ``` ## Key Patterns 1. **Encapsulation**: Hide unsafe behind safe public API 2. **Invariant maintenance**: Use private fields to maintain invariants 3. **PhantomData**: Track lifetimes and ownership for pointers 4. **RAII**: Use Drop for cleanup 5. **Type state**: Use types to encode valid states