indexing/
lib.rs

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