Skip to content

API Proposal: Slice iterator helpers for taking chunks of data #616

@ObsidianMinor

Description

@ObsidianMinor

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

No one assigned

    Labels

    T-libs-apiapi-change-proposalA proposal to add or alter unstable APIs in the standard libraries

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions