Creating slices

All the examples I see of creating slices use range syntax. Is it true that that is the only way to create a slice? I don't think I have seen any methods on built-in types that return a slice, but maybe I just overlooked them.

Do Vec::as_slice and similar ones count?

While technically that creates a slice, I'm looking for ways to create slices that represent a subset of a collection rather than all of it.

Slice is, by definition, a contiguous chunk of memory. So any way to create a subslice of collection will be semantically equivalent to indexing with range.

This is exactly what I'm looking for ... other ways to do this besides using a range. What other ways exist?

With unsafe you can create a slice through std::slice::from_raw_parts by providing a pointer and length.

1 Like

You can also use from_ref to turn an &T into a slice &[T] of length one.

1 Like

At the end of the day, a slice is just a pointer to the first element and a length, so all methods of creating one will reduce to some pointer arithmetic (to calculate where the first element is) and bounds check (to make sure the slice doesn't include invalid memory) in some form or another.

Everything else is just smoke and mirrors and layers of abstraction to help maintain memory safety, correctness, and ergonomics.

In the case of &some_slice[start..end], this expression boils down to this piece of code:

unsafe impl<T> SliceIndex<[T]> for ops::Range<usize> {
    type Output = [T];

    #[inline]
    fn get(self, slice: &[T]) -> Option<&[T]> {
        if self.start > self.end || self.end > slice.len() {
            None
        } else {
            // SAFETY: `self` is checked to be valid and in bounds above.
            unsafe { Some(&*self.get_unchecked(slice)) }
        }
    }

    #[inline]
    unsafe fn get_unchecked(self, slice: *const [T]) -> *const [T] {
        // SAFETY: the caller guarantees that `slice` is not dangling, so it
        // cannot be longer than `isize::MAX`. They also guarantee that
        // `self` is in bounds of `slice` so `self` cannot overflow an `isize`,
        // so the call to `add` is safe.
        unsafe { ptr::slice_from_raw_parts(slice.as_ptr().add(self.start), self.end - self.start) }
    }
}

Where ptr::slice_from_raw_parts() (who's stable form callable via std::slice::from_raw_parts()) is defined by unsafe-ly reinterpreting the pointer and length as a raw slice.

#[repr(C)]
union Repr<T> {
    rust: *const [T],
    rust_mut: *mut [T],
    raw: FatPtr<T>,
}

#[repr(C)]
struct FatPtr<T> {
    data: *const T,
    len: usize,
}

pub const fn slice_from_raw_parts<T>(data: *const T, len: usize) -> *const [T] {
    // SAFETY: Accessing the value from the `Repr` union is safe since *const [T]
    // and FatPtr have the same memory layouts. Only std can make this
    // guarantee.
    unsafe { Repr { raw: FatPtr { data, len } }.rust }
}
4 Likes

Many slice methods return further slices or iterators over the same (e.g. chunks).

3 Likes

I'm looking for ways to create slices that represent a subset of a collection rather than all of it.

A long slice can be shortened, so std types don't bother having methods for subsets. You can get a full-sized slice and then use the range syntax to shorten it. Since it's a cheap operation and a built-in syntax, there's no need for types to provide custom methods for it.

2 Likes

Also technically you could call iter on a slice, consume part of the iterator, and then call as_slice on it to get the remaining slice (i.e. without the yielded items). However, please don't do this.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.