-
Notifications
You must be signed in to change notification settings - Fork 22
Open
Labels
T-libs-apiapi-change-proposalA proposal to add or alter unstable APIs in the standard librariesA proposal to add or alter unstable APIs in the standard libraries
Description
Problem statement
Parsing with slice iterators makes parsing easy! As long as you're going element by element... What if I want to read a whole chunk of data as a slice and advance the iterator?
Motivating examples or use cases
When parsing binary formats, often blobs of data will be prefixed by their length.
let bytes = &[3, 72, 105, 33];
let mut bytes_iter = bytes.iter();
let length = bytes_iter.next()? as usize; // single byte length
// read string all at once... normally this requires converting *back* into a slice, splitting, and reassigning
let (string, remainder) = bytes_iter.as_slice().split_at_checked(length)?;
bytes_iter = remainder.iter();
// now repeat this every single time you want to read a chunk of data.
Or fixed sized data will be somehow tagged, meaning you know exactly how much data you want, like reading an integer.
let bytes = &[55, 1, 0, 0, 0];
let mut bytes_iter = bytes.iter();
let tag = bytes_iter.next()?; // this somehow indicates we expect an LE u32
// read byte array... same thing as before but now we use `split_first_chunk`
let (value_array, remainder) = bytes_iter.as_slice().split_first_chunk<4>()?;
bytes_iter = remainder.iter();
// repeat
Solution sketch
Two simple APIs: take_slice
and take_array
.
impl<'a, T> core::slice::Iter<'a, T> {
pub fn take_slice(&mut self, len: usize) -> Option<&'a [T]> {
// Returns a slice of the data from the current point up to the given length, advancing the iterator.
// If the iterator is shorter than `len`, this returns None and does not advance the iterator.
}
pub fn take_array<const N: usize>(&mut self) -> Option<&'a [T; N]> {
// Returns an array of the data from the current point up to the given length, advancing the iterator.
// If the iterator is shorter than `len`, this returns None and does not advance the iterator.
}
}
impl<'a, T> core::slice::IterMut<'a, T> {
pub fn take_slice(&mut self, len: usize) -> Option<&'a mut [T]> {
// Returns a slice of the data from the current point up to the given length, advancing the iterator.
// If the iterator is shorter than `len`, this returns None and does not advance the iterator.
}
pub fn take_array<const N: usize>(&mut self) -> Option<&'a mut [T; N]> {
// Returns an array of the data from the current point up to the given length, advancing the iterator.
// If the iterator is shorter than `len`, this returns None and does not advance the iterator.
}
}
Solution examples
let bytes = &[3, 72, 105, 33];
let mut bytes_iter = bytes.iter();
let length = bytes_iter.next()? as usize;
let string = bytes_iter.take_slice(length)?;
let bytes = &[55, 1, 0, 0, 0];
let mut bytes_iter = bytes.iter();
let tag = bytes_iter.next()?;
let value = u32::from_le_bytes(bytes_iter.take_array()?);
Alternatives
As shown in the examples, you can sort-of do this with the existing core::slice::Iter::as_slice()
function, though it's not ideal.
// For slices:
let (new_slice, remainder) = iter.as_slice().split_at_checked(length)?;
iter = remainder.iter();
// For arrays:
let (new_array, remainder) = bytes_iter.as_slice().split_first_chunk<4>()?;
iter = remainder.iter();
It gets more complicated with IterMut
.
// For slices:
let (new_slice, remainder) = core::mem::replace(&mut iter, (&mut []).iter_mut()).into_slice().split_at_mut_checked(length)?;
iter = remainter.iter_mut();
// For arrays:
let (new_slice, remainder) = core::mem::replace(&mut iter, (&mut []).iter_mut()).into_slice().split_first_chunk_mut<N>()?;
iter = remainter.iter_mut();
Metadata
Metadata
Assignees
Labels
T-libs-apiapi-change-proposalA proposal to add or alter unstable APIs in the standard librariesA proposal to add or alter unstable APIs in the standard libraries