unchecked_index/
lib.rs

1
2//! Unchecked indexing through the regular index syntax.
3//!
4//! Using a wrapper type that requires an `unsafe` block to create.
5//!
6//! *Note:* All unchecked indexing here is actually “checked” with *debug
7//! assertions* when they are enabled (they are off by default in release
8//! builds). This is a feature! Debug checking does **not** make your code safe,
9//! but it helps finding bugs in `unsafe` code. Test your code responsibly.
10//!
11//! # Example
12//!
13//! ```rust
14//!
15//! use unchecked_index::unchecked_index;
16//!
17//! /// unsafe because: trusts the permutation to be correct
18//! unsafe fn apply_permutation<T>(perm: &mut [usize], v: &mut [T]) {
19//!     debug_assert_eq!(perm.len(), v.len());
20//!     
21//!     // use unchecked (in reality, debug-checked) indexing throughout
22//!     let mut perm = unchecked_index(perm);
23//!     
24//!     for i in 0..perm.len() {
25//!         let mut current = i;
26//!         while i != perm[current] {
27//!             let next = perm[current];
28//!             // move element from next to current
29//!             v.swap(next, current);
30//!             perm[current] = current;
31//!             current = next;
32//!         }
33//!         perm[current] = current;
34//!     }
35//! }
36//! ```
37//!
38//! # Crate Features
39//!
40//! This crate is always `#![no_std]`.
41//!
42//! # Rust Version
43//!
44//! This version of the crate requires Rust 1.15 or later.
45
46#![cfg_attr(not(test), no_std)]
47
48#[cfg(not(test))]
49extern crate core as std;
50
51/// Wrapper type for unchecked indexing through the regular index syntax
52///
53/// Note that the indexing is checked with debug assertions, but unchecked
54/// in release mode. Test your code responsibly.
55#[derive(Copy)]
56pub struct UncheckedIndex<S>(S);
57
58impl<S: Copy> Clone for UncheckedIndex<S> {
59    fn clone(&self) -> Self { *self }
60}
61
62/// Create a new unchecked indexing wrapper.
63///
64/// This function is `unsafe` to call because it allows all further indexing
65/// on the wrapper to omit bounds checks.
66///
67/// # Safety
68///
69/// The caller must ensure that **all** indexing of the resulting
70/// `UncheckedIndex` wrapper is in bounds of the underlying container.
71pub unsafe fn unchecked_index<T>(v: T) -> UncheckedIndex<T>
72{
73    UncheckedIndex(v)
74}
75
76/// Access the element(s) at `index`, without bounds checks!
77///
78/// *Note:* Will use *debug assertions* to check that the index is actually
79/// valid. In release mode, debug assertions are *off* by default.
80///
81/// # Safety
82///
83/// The caller must ensure that `index` is in bounds of the underlying
84/// container.
85pub unsafe fn get_unchecked<T: ?Sized, I>(v: &T, index: I) -> &T::Output
86    where T: GetUnchecked<I>
87{
88    #[cfg(debug_assertions)]
89    v.assert_indexable_with(&index);
90    v.get_unchecked(index)
91}
92
93/// Access the element(s) at `index`, without bounds checks!
94///
95/// *Note:* Will use *debug assertions* to check that the index is actually
96/// valid. In release mode, debug assertions are *off* by default.
97///
98/// # Safety
99///
100/// The caller must ensure that `index` is in bounds of the underlying
101/// container.
102pub unsafe fn get_unchecked_mut<T: ?Sized, I>(v: &mut T, index: I) -> &mut T::Output
103    where T: GetUncheckedMut<I>
104{
105    #[cfg(debug_assertions)]
106    v.assert_indexable_with(&index);
107    v.get_unchecked_mut(index)
108}
109
110use std::ops::{Deref, DerefMut, Index, IndexMut};
111
112impl<T> Deref for UncheckedIndex<T> {
113    type Target = T;
114    fn deref(&self) -> &T {
115        &self.0
116    }
117}
118
119impl<T> DerefMut for UncheckedIndex<T> {
120    fn deref_mut(&mut self) -> &mut T {
121        &mut self.0
122    }
123}
124
125impl<T, I> Index<I> for UncheckedIndex<T>
126    where T: GetUnchecked<I>
127{
128    type Output = T::Output;
129
130    /// Access the element(s) at `index`, without bounds checks!
131    ///
132    /// *Note:* Will use *debug assertions* to check that the index is actually
133    /// valid. In release mode, debug assertions are *off* by default.
134    ///
135    /// # Safety
136    ///
137    /// The caller must ensure that `index` is always in bounds of the
138    /// underlying container.
139    #[inline]
140    fn index(&self, index: I) -> &Self::Output {
141        unsafe {
142            get_unchecked(&self.0, index)
143        }
144    }
145}
146
147impl<T, I> IndexMut<I> for UncheckedIndex<T>
148    where T: GetUncheckedMut<I>
149{
150    /// Access the element(s) at `index`, without bounds checks!
151    ///
152    /// *Note:* Will use *debug assertions* to check that the index is actually
153    /// valid. In release mode, debug assertions are *off* by default.
154    ///
155    /// # Safety
156    ///
157    /// The caller must ensure that `index` is always in bounds of the
158    /// underlying container.
159    #[inline]
160    fn index_mut(&mut self, index: I) -> &mut Self::Output {
161        unsafe {
162            get_unchecked_mut(&mut self.0, index)
163        }
164    }
165}
166
167pub trait CheckIndex<I> {
168    /// Assert (using a regular assertion) that the index is valid.
169    /// Must not return if the index is invalid for indexing self.
170    ///
171    /// ***Panics*** if `index` is invalid.
172    fn assert_indexable_with(&self, index: &I);
173}
174
175impl<'a, T: ?Sized, I> CheckIndex<I> for &'a T where T: CheckIndex<I> {
176    fn assert_indexable_with(&self, index: &I) {
177        (**self).assert_indexable_with(index)
178    }
179}
180
181impl<'a, T: ?Sized, I> CheckIndex<I> for &'a mut T where T: CheckIndex<I> {
182    fn assert_indexable_with(&self, index: &I) {
183        (**self).assert_indexable_with(index)
184    }
185}
186
187pub trait GetUnchecked<I>: CheckIndex<I> {
188    type Output: ?Sized;
189    unsafe fn get_unchecked(&self, index: I) -> &Self::Output;
190}
191
192pub trait GetUncheckedMut<I>: GetUnchecked<I> {
193    unsafe fn get_unchecked_mut(&mut self, index: I) -> &mut Self::Output;
194}
195
196impl<'a, T: ?Sized, I> GetUnchecked<I> for &'a T
197    where T: GetUnchecked<I>
198{
199    type Output = T::Output;
200    unsafe fn get_unchecked(&self, index: I) -> &Self::Output {
201        (**self).get_unchecked(index)
202    }
203}
204
205impl<'a, T: ?Sized, I> GetUnchecked<I> for &'a mut T
206    where T: GetUnchecked<I>
207{
208    type Output = T::Output;
209    unsafe fn get_unchecked(&self, index: I) -> &Self::Output {
210        (**self).get_unchecked(index)
211    }
212}
213
214impl<'a, T: ?Sized, I> GetUncheckedMut<I> for &'a mut T
215    where T: GetUncheckedMut<I>
216{
217    unsafe fn get_unchecked_mut(&mut self, index: I) -> &mut Self::Output {
218        (**self).get_unchecked_mut(index)
219    }
220}
221
222mod slice_impls;
223
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn it_works() {
231        let mut data = [0; 8];
232        unsafe {
233            let mut data = unchecked_index(&mut data);
234            for i in 0..data.len() {
235                data[i] = i;
236            }
237        }
238        assert_eq!(data, [0, 1, 2, 3, 4, 5, 6, 7]);
239    }
240
241    #[cfg(debug_assertions)]
242    #[test]
243    #[should_panic]
244    fn debug_oob_check_write() {
245        let mut data = [0; 8];
246        unsafe {
247            let mut data = unchecked_index(&mut data[..7]);
248            data[7] = 1;
249        }
250    }
251
252    #[cfg(debug_assertions)]
253    #[test]
254    #[should_panic]
255    fn debug_oob_check_read() {
256        let mut data = [0; 8];
257        unsafe {
258            let data = unchecked_index(&mut data[..7]);
259            println!("{}", data[17]);
260        }
261    }
262
263    #[cfg(not(debug_assertions))]
264    #[test]
265    fn non_debug_oob() {
266        // outside bounds of the slice but not the data -- should be ok
267        let mut data = [0; 8];
268        unsafe {
269            let mut data = unchecked_index(&mut data[..7]);
270            data[7] = 1;
271        }
272        assert_eq!(data, [0, 0, 0, 0, 0, 0, 0, 1]);
273    }
274
275    #[cfg(debug_assertions)]
276    #[test]
277    #[should_panic]
278    fn debug_oob_check_read_slice_1() {
279        let mut data = [0; 8];
280        unsafe {
281            let data = unchecked_index(&mut data[..7]);
282            println!("{:?}", &data[5..10]);
283        }
284    }
285
286    #[cfg(debug_assertions)]
287    #[test]
288    #[should_panic]
289    fn debug_oob_check_read_slice_2() {
290        let mut data = [0; 8];
291        unsafe {
292            let data = unchecked_index(&mut data[..7]);
293            println!("{:?}", &data[7..6]);
294        }
295    }
296
297    #[cfg(debug_assertions)]
298    #[test]
299    #[should_panic]
300    fn debug_oob_check_read_slice_3() {
301        let mut data = [0; 8];
302        unsafe {
303            let data = unchecked_index(&mut data[..7]);
304            println!("{:?}", &data[8..]);
305        }
306    }
307
308    #[cfg(debug_assertions)]
309    #[test]
310    #[should_panic]
311    fn debug_oob_check_read_slice_4() {
312        let mut data = [0; 8];
313        unsafe {
314            let data = unchecked_index(&mut data[..7]);
315            println!("{:?}", &data[..9]);
316        }
317    }
318
319    #[cfg(not(debug_assertions))]
320    #[test]
321    fn non_debug_oob_slice() {
322        // outside bounds of the slice but not the data -- should be ok
323        let mut data = [0; 8];
324        unsafe {
325            let mut data = unchecked_index(&mut data[..7]);
326            data[7..8][0] = 1;
327        }
328        assert_eq!(data, [0, 0, 0, 0, 0, 0, 0, 1]);
329    }
330}