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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
#![no_std]
//! This crate impements a slice that can seal its initial part off from mutability, and hand out
//! the sealed parts as immutable references.
//!
//! A typical application is message processing in constrained environments where some
//! normalization is done in-place (because no message copy can be allocated), and the message is
//! sent off piecewise for further processing.
//!
//! A stand-alone usage example that capitalizes text in place and passes it out word by word:
//!
//! ```
//! let mut text = b"lorem ipsum dolor sit amet".to_vec();
//! let mut s = sealingslice::SealingSlice::new(&mut text[..]);
//! let mut capitalized = vec![];
//! let mut lastword = 0;
//! loop {
//! let n;
//! {
//! let tail = s.mutable();
//!
//! if tail.len() == 0 {
//! break;
//! }
//!
//! // 'a' to 'z'
//! if tail[0] >= 0x61 && tail[0] <= 0x7a {
//! // ASCII shift to capital letter
//! tail[0] -= 0x20;
//! }
//!
//! let space = tail.iter().position(|&c| c == 0x20);
//! let space = space.unwrap_or(tail.len());
//! n = std::cmp::min(space + 1, tail.len());
//! }
//! s.seal(n);
//!
//! capitalized.push(std::str::from_utf8(&s.sealed()[lastword..]).unwrap());
//! lastword = s.sealed().len();
//! }
//! assert_eq!(capitalized, &["Lorem ", "Ipsum ", "Dolor ", "Sit ", "Amet"]);
//! ```
/// A slice that can seal parts of itself off from mutability, and hand out the sealed parts as
/// immutable references.
///
/// The slice grows immutable from start to end in a contiguous way, is initially mutable as a
/// whole. The seal is moved forward using the [.seal(n)](SealingSlice::seal()) method. The sealed
/// and unsealed parts can be accessed with the [.sealed()](SealingSlice::sealed()) and
/// [.mutable()](SealingSlice::mutable()) methods, respectively.
pub struct SealingSlice<'s, T: 's> {
head: &'s [T],
tail: &'s mut [T],
}
impl<'s, T> SealingSlice<'s, T> {
/// Create a new `SealingSlice`. The slice is initially fully mutable.
pub fn new(slice: &'s mut [T]) -> Self {
SealingSlice { head: &[], tail: slice }
}
/// Seal off `n` more items in the slice.
///
/// Like out-of-bounds slice access, this panics if n is less than `.mutable().len()` -- thus
/// ensuring that `.sealed()` and `.mutable()` will not.
pub fn seal(&mut self, n: usize) {
self.try_seal(n).expect("Seal operation exceeds slice length");
}
/// Seal off `n` more items in the slice.
///
/// Unlike `.seal()`, this returns Err(()) if too many items are requested for sealing, leaving
/// the sealing cursor intact.
pub fn try_seal(&mut self, n: usize) -> Result<(), ()> {
if n > self.tail.len() {
return Err(())
}
let (newly_sealed, tail) = core::mem::replace(&mut self.tail, &mut []).split_at_mut(n);
self.tail = tail;
self.head = coalesce(self.head, newly_sealed);
Ok(())
}
/// Obtain the part of the slice that has not yet been sealed. That is a mutable view on the
/// original slice, ends at its end, and always has the length of the original slice minus the
/// sum of all sealed parts.
pub fn mutable<'a>(&'a mut self) -> &'a mut [T] {
self.tail
}
/// Obtain the sealed part of the slice. This is an immutable view on the original slice,
/// starts at its beginning, and is always as long as the sum of all previous `.seal(n)`
/// arguments.
pub fn sealed<'a>(&'a self) -> &'s [T] {
self.head
}
}
/// Given two slices, assert that they are contiguous and return a slice over both of them
fn coalesce<'a, T>(head: &'a [T], tail: &'a [T]) -> &'a [T] {
if head.len() == 0 {
return tail;
}
// Using wrapping_offset because we can't tell where the memory comes from, and a 16-bit
// platform might do this on its comparatively large network buffer.
assert!(head.as_ptr().wrapping_offset(head.len() as isize) == tail.as_ptr());
// Unsafe: The parts have been shown to be contiguous, thus any offset inside them can be
// dereferenced for the lifetime of the output, for which we've taken outliving lifetimes as
// input.
unsafe { core::slice::from_raw_parts(head.as_ptr(), head.len() + tail.len()) }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn seal_nothing() {
let mut data = [0, 1, 2].to_vec();
let mut s = SealingSlice::new(data.as_mut());
s.seal(0);
assert_eq!(s.mutable(), &[0, 1, 2]);
assert_eq!(s.sealed(), &[]);
}
#[test]
fn seal_all() {
let mut data = [0, 1, 2].to_vec();
let mut s = SealingSlice::new(data.as_mut());
s.seal(3);
assert_eq!(s.mutable(), &[]);
assert_eq!(s.sealed(), &[0, 1, 2]);
}
#[test]
#[should_panic]
fn seal_too_much() {
let mut data = [0, 1, 2].to_vec();
let mut s = SealingSlice::new(data.as_mut());
s.seal(4);
}
#[test]
fn seal_too_much_carefully() {
let mut data = [0, 1, 2].to_vec();
let mut s = SealingSlice::new(data.as_mut());
assert_eq!(s.try_seal(4), Err(()));
assert_eq!(s.try_seal(3), Ok(()));
}
#[test]
fn seal_some() {
let mut data = [0, 1, 2].to_vec();
let mut s = SealingSlice::new(data.as_mut());
s.seal(2);
assert_eq!(s.mutable(), &[2]);
assert_eq!(s.sealed(), &[0, 1]);
s.mutable()[0] = 4;
s.seal(1);
assert_eq!(s.sealed(), &[0, 1, 4]);
}
#[test]
/// We don't guarantee that the slices are identical, but it's the only way it makes sense, and
/// if that's not the case, something is probably wrong.
fn identical_ptrs() {
let mut data = [0, 1, 2].to_vec();
let dataptr = data.as_ref() as *const _;
let datalen = data.len();
let mut s = SealingSlice::new(data.as_mut());
assert_eq!(s.mutable() as *const _, dataptr);
assert_eq!(s.mutable().len(), datalen);
s.seal(1);
s.seal(2);
assert_eq!(s.sealed() as *const _, dataptr);
assert_eq!(s.sealed().len(), datalen);
}
}