1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
//! Sound unchecked indexing using the techniques from generative lifetimes, //! extended to string slices and without pointer or mutability support. //! //! Major kudos go to Gankro and especially Bluss for the original [indexing] //! crate, from which this crate blatantly steals all of its cleverness. //! //! # Basic Structure //! //! - A scope is created using the [`scope`] function; inside this scope, //! there is a [`Container`] object that has two roles: (1) it gives out or //! vets trusted indices, pointers and ranges (2) it provides access to the //! underlying data through these indices and ranges. //! //! - The container and its indices and ranges are “branded” with a lifetime //! parameter `'id` which is an identity marker. Branded items //! can't leave their scope, and they tie the items uniquely to a particular //! container. This makes it possible to trust them. //! //! - `Index<'id>` is a trusted index //! - `Range<'id, Emptiness>` is a trusted range. //! //! - For a range, if the proof parameter `Emptiness` is `NonEmpty`, then the //! range is known to have at least one element. An observation: A non-empty //! range always has a valid front index, so it is interchangeable with the //! index representation. //! //! - Indices also use the same proof parameter. A `NonEmpty` index points to a //! valid element, while an `Unknown` index is an edge index (it can be used //! to slice the container, but not to dereference to an element). //! //! - All ranges have a `.first()` method to get the first index in the range, //! but it's only when the range is nonempty that the returned index is also //! `NonEmpty` and thus dereferenceable. //! //! # Borrowing //! //! - Indices and ranges are freely copyable and do not track the backing data //! themselves. All access to the underlying data goes through the //! [`Container`] (e.g. by indexing the container with a trusted index). //! //! [indexing]: <https://github.com/bluss/indexing> #![no_std] #![deny(rust_2018_idioms, unconditional_recursion)] #![cfg_attr(feature = "doc", feature(doc_cfg))] #[cfg(feature = "std")] extern crate std; mod container; mod index; pub mod proof; pub mod traits; pub mod utf8; use crate::traits::TrustedContainer; pub use crate::{ container::Container, index::{Index, IndexError, Range}, }; /// Create an indexing scope for a container. /// /// The indexing scope is a closure that is passed a unique lifetime for the /// parameter `'id`; this lifetime brands the container and its indices and /// ranges, so that they are trusted to be in bounds. /// /// Indices and ranges branded with `'id` cannot leave the closure. The /// container can only be accessed through the `Container` wrapper passed as /// the first argument to the indexing scope. pub fn scope<Array: TrustedContainer, F, Out>(array: Array, f: F) -> Out where F: for<'id> FnOnce(Container<'id, Array>) -> Out, { // This is where the magic happens. We bind the indexer and indices to the // same invariant lifetime (a constraint established by F's definition). // As such, each call to `indices` produces a unique signature that only // these two values can share. // // Within this function, the borrow solver can choose literally any // lifetime, including `'static`, but we don't care what the borrow solver // does in *this* function. We only need to trick the solver in the // caller's scope. Since borrowck doesn't do interprocedural analysis, it // sees every call to this function produces values with some opaque fresh // lifetime and can't unify any of them. // // In principle a "super borrowchecker" that does interprocedural analysis // would break this design, but we could go out of our way to somehow bind // the lifetime to the inside of this function, making it sound again. // Rustc will never do such analysis, so we don't care. f(unsafe { Container::new(array) }) } /// [`scope`], but for a backing container behind a reference /// (such as an unsized string slice). pub fn scope_ref<Array: TrustedContainer, F, Out>(array: &Array, f: F) -> Out where F: for<'id> FnOnce(&'id Container<'id, Array>) -> Out, { f(unsafe { &*(array as *const Array as *const Container<'_, Array>) }) }