Skip to content

Commit

Permalink
Make <[T]>::array_* methods fail to compile on 0 len arrays
Browse files Browse the repository at this point in the history
Methods updated:
* `array_windows`
* `array_chunks`
* `array_chunks_mut`
* `as_chunks`
* `as_chunks_mut`
* `as_chunks_unchecked`
* `as_rchunks`
* `as_rchunks_mut`
* `as_chunks_unchecked_mut`

I implemented this using compile time assertions.

Example compilation error:

```
> rustc +stage1 .\improper_array_windows.rs --crate-type=rlib
error[E0080]: evaluation of `core::slice::<impl [u32]>::array_windows::<0>::{constant#0}` failed
    --> J:\rust_lang\library\core\src\slice\mod.rs:1381:13
     |
1381 |             assert!(N != 0, "window size must be non-zero");
     |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'window size must be non-zero', J:\rust_lang\library\core\src\slice\mod.rs:1381:13
     |
     = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)

note: the above error was encountered while instantiating `fn core::slice::<impl [u32]>::array_windows::<0>`
 --> .\improper_array_windows.rs:4:14
  |
4 |     for _ in s.array_windows::<0>(){
  |              ^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error
```

I also added doctests and adjusted documentation.

Relates:
#74985
#75027
  • Loading branch information
AngelicosPhosphoros committed May 20, 2023
1 parent 25f084d commit 7bb2c00
Showing 1 changed file with 121 additions and 47 deletions.
168 changes: 121 additions & 47 deletions library/core/src/slice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,6 @@ impl<T> [T] {
///
/// This may only be called when
/// - The slice splits exactly into `N`-element chunks (aka `self.len() % N == 0`).
/// - `N != 0`.
///
/// # Examples
///
Expand All @@ -990,14 +989,28 @@ impl<T> [T] {
///
/// // These would be unsound:
/// // let chunks: &[[_; 5]] = slice.as_chunks_unchecked() // The slice length is not a multiple of 5
/// // let chunks: &[[_; 0]] = slice.as_chunks_unchecked() // Zero-length chunks are never allowed
/// ```
///
/// It doesn't compile if chunk size is zero:
/// ```compile_fail
/// #![feature(slice_as_chunks)]
/// const N: usize = 0;
/// let slice = [1, 2, 3, 4];
/// let _ = unsafe{
/// slice.as_chunks_unchecked::<N>()
/// };
/// ```
///
#[unstable(feature = "slice_as_chunks", issue = "74985")]
#[inline]
#[must_use]
pub const unsafe fn as_chunks_unchecked<const N: usize>(&self) -> &[[T; N]] {
const {
assert!(N != 0, "chunk size must be non-zero");
}
let this = self;
// SAFETY: Caller must guarantee that `N` is nonzero and exactly divides the slice length
// SAFETY: Caller must guarantee that `N` exactly divides the slice length
// `N` cannot be zero because we checked it using compile assert above.
let new_len = unsafe {
assert_unsafe_precondition!(
"slice::as_chunks_unchecked requires `N != 0` and the slice to split exactly into `N`-element chunks",
Expand All @@ -1014,11 +1027,6 @@ impl<T> [T] {
/// starting at the beginning of the slice,
/// and a remainder slice with length strictly less than `N`.
///
/// # Panics
///
/// Panics if `N` is 0. This check will most probably get changed to a compile time
/// error before this method gets stabilized.
///
/// # Examples
///
/// ```
Expand All @@ -1039,16 +1047,28 @@ impl<T> [T] {
/// };
/// assert_eq!(chunks, &[['R', 'u'], ['s', 't']]);
/// ```
///
/// It doesn't compile if chunk size is zero:
/// ```compile_fail
/// #![feature(slice_as_chunks)]
/// const N: usize = 0;
/// let slice = [1, 2, 3, 4];
/// let _ = slice.as_chunks::<N>();
/// ```
///
#[unstable(feature = "slice_as_chunks", issue = "74985")]
#[inline]
#[track_caller]
#[must_use]
pub const fn as_chunks<const N: usize>(&self) -> (&[[T; N]], &[T]) {
assert!(N != 0, "chunk size must be non-zero");
const {
assert!(N != 0, "chunk size must be non-zero");
}
let len = self.len() / N;
let (multiple_of_n, remainder) = self.split_at(len * N);
// SAFETY: We already panicked for zero, and ensured by construction
// SAFETY: It is ensured by construction
// that the length of the subslice is a multiple of N.
// `N` cannot be zero because we checked it using compile assert above.
let array_slice = unsafe { multiple_of_n.as_chunks_unchecked() };
(array_slice, remainder)
}
Expand All @@ -1071,16 +1091,28 @@ impl<T> [T] {
/// assert_eq!(remainder, &['l']);
/// assert_eq!(chunks, &[['o', 'r'], ['e', 'm']]);
/// ```
///
/// It doesn't compile if chunk size is zero:
/// ```compile_fail
/// #![feature(slice_as_chunks)]
/// const N: usize = 0;
/// let slice = [1, 2, 3, 4];
/// let _ = slice.as_rchunks::<N>();
/// ```
///
#[unstable(feature = "slice_as_chunks", issue = "74985")]
#[inline]
#[track_caller]
#[must_use]
pub const fn as_rchunks<const N: usize>(&self) -> (&[T], &[[T; N]]) {
assert!(N != 0, "chunk size must be non-zero");
const {
assert!(N != 0, "chunk size must be non-zero");
}
let len = self.len() / N;
let (remainder, multiple_of_n) = self.split_at(self.len() - len * N);
// SAFETY: We already panicked for zero, and ensured by construction
// SAFETY: It is ensured by construction
// that the length of the subslice is a multiple of N.
// `N` cannot be zero because we checked it using compile assert above.
let array_slice = unsafe { multiple_of_n.as_chunks_unchecked() };
(remainder, array_slice)
}
Expand All @@ -1094,11 +1126,6 @@ impl<T> [T] {
///
/// This method is the const generic equivalent of [`chunks_exact`].
///
/// # Panics
///
/// Panics if `N` is 0. This check will most probably get changed to a compile time
/// error before this method gets stabilized.
///
/// # Examples
///
/// ```
Expand All @@ -1111,12 +1138,22 @@ impl<T> [T] {
/// assert_eq!(iter.remainder(), &['m']);
/// ```
///
/// It doesn't compile if chunk size is zero:
/// ```compile_fail
/// #![feature(array_chunks)]
/// const N: usize = 0;
/// let slice = [1, 2, 3, 4];
/// let _ = slice.array_chunks::<N>();
/// ```
///
/// [`chunks_exact`]: slice::chunks_exact
#[unstable(feature = "array_chunks", issue = "74985")]
#[inline]
#[track_caller]
pub fn array_chunks<const N: usize>(&self) -> ArrayChunks<'_, T, N> {
assert!(N != 0, "chunk size must be non-zero");
const {
assert!(N != 0, "chunk size must be non-zero");
}
ArrayChunks::new(self)
}

Expand All @@ -1127,7 +1164,6 @@ impl<T> [T] {
///
/// This may only be called when
/// - The slice splits exactly into `N`-element chunks (aka `self.len() % N == 0`).
/// - `N != 0`.
///
/// # Examples
///
Expand All @@ -1147,14 +1183,28 @@ impl<T> [T] {
///
/// // These would be unsound:
/// // let chunks: &[[_; 5]] = slice.as_chunks_unchecked_mut() // The slice length is not a multiple of 5
/// // let chunks: &[[_; 0]] = slice.as_chunks_unchecked_mut() // Zero-length chunks are never allowed
/// ```
///
/// It doesn't compile if chunk size is zero:
/// ```compile_fail
/// #![feature(slice_as_chunks)]
/// const N: usize = 0;
/// let mut slice = [1, 2, 3, 4];
/// let _ = unsafe{
/// slice.as_chunks_unchecked_mut::<N>();
/// };
/// ```
///
#[unstable(feature = "slice_as_chunks", issue = "74985")]
#[inline]
#[must_use]
pub const unsafe fn as_chunks_unchecked_mut<const N: usize>(&mut self) -> &mut [[T; N]] {
const {
assert!(N != 0, "chunk size must be non-zero");
}
let this = &*self;
// SAFETY: Caller must guarantee that `N` is nonzero and exactly divides the slice length
// SAFETY: Caller must guarantee that `N` exactly divides the slice length
// `N` cannot be zero because we checked it using compile assert above.
let new_len = unsafe {
assert_unsafe_precondition!(
"slice::as_chunks_unchecked_mut requires `N != 0` and the slice to split exactly into `N`-element chunks",
Expand All @@ -1171,11 +1221,6 @@ impl<T> [T] {
/// starting at the beginning of the slice,
/// and a remainder slice with length strictly less than `N`.
///
/// # Panics
///
/// Panics if `N` is 0. This check will most probably get changed to a compile time
/// error before this method gets stabilized.
///
/// # Examples
///
/// ```
Expand All @@ -1191,16 +1236,28 @@ impl<T> [T] {
/// }
/// assert_eq!(v, &[1, 1, 2, 2, 9]);
/// ```
///
/// It doesn't compile if chunk size is zero:
/// ```compile_fail
/// #![feature(slice_as_chunks)]
/// const N: usize = 0;
/// let mut slice = [1, 2, 3, 4];
/// let _ = slice.as_chunks_mut::<N>();
/// ```
///
#[unstable(feature = "slice_as_chunks", issue = "74985")]
#[inline]
#[track_caller]
#[must_use]
pub const fn as_chunks_mut<const N: usize>(&mut self) -> (&mut [[T; N]], &mut [T]) {
assert!(N != 0, "chunk size must be non-zero");
const {
assert!(N != 0, "chunk size must be non-zero");
}
let len = self.len() / N;
let (multiple_of_n, remainder) = self.split_at_mut(len * N);
// SAFETY: We already panicked for zero, and ensured by construction
// SAFETY: It is ensured by construction
// that the length of the subslice is a multiple of N.
// `N` cannot be zero because we checked it using compile assert above.
let array_slice = unsafe { multiple_of_n.as_chunks_unchecked_mut() };
(array_slice, remainder)
}
Expand All @@ -1209,11 +1266,6 @@ impl<T> [T] {
/// starting at the end of the slice,
/// and a remainder slice with length strictly less than `N`.
///
/// # Panics
///
/// Panics if `N` is 0. This check will most probably get changed to a compile time
/// error before this method gets stabilized.
///
/// # Examples
///
/// ```
Expand All @@ -1229,16 +1281,28 @@ impl<T> [T] {
/// }
/// assert_eq!(v, &[9, 1, 1, 2, 2]);
/// ```
///
/// It doesn't compile if chunk size is zero:
/// ```compile_fail
/// #![feature(slice_as_chunks)]
/// const N: usize = 0;
/// let mut slice = [1, 2, 3, 4];
/// let _ = slice.as_rchunks_mut::<N>();
/// ```
///
#[unstable(feature = "slice_as_chunks", issue = "74985")]
#[inline]
#[track_caller]
#[must_use]
pub const fn as_rchunks_mut<const N: usize>(&mut self) -> (&mut [T], &mut [[T; N]]) {
assert!(N != 0, "chunk size must be non-zero");
const {
assert!(N != 0, "chunk size must be non-zero");
}
let len = self.len() / N;
let (remainder, multiple_of_n) = self.split_at_mut(self.len() - len * N);
// SAFETY: We already panicked for zero, and ensured by construction
// SAFETY: It is ensured by construction
// that the length of the subslice is a multiple of N.
// `N` cannot be zero because we checked it using compile assert above.
let array_slice = unsafe { multiple_of_n.as_chunks_unchecked_mut() };
(remainder, array_slice)
}
Expand All @@ -1252,11 +1316,6 @@ impl<T> [T] {
///
/// This method is the const generic equivalent of [`chunks_exact_mut`].
///
/// # Panics
///
/// Panics if `N` is 0. This check will most probably get changed to a compile time
/// error before this method gets stabilized.
///
/// # Examples
///
/// ```
Expand All @@ -1271,12 +1330,22 @@ impl<T> [T] {
/// assert_eq!(v, &[1, 1, 2, 2, 0]);
/// ```
///
/// It doesn't compile if chunk size is zero:
/// ```compile_fail
/// #![feature(array_chunks)]
/// const N: usize = 0;
/// let mut slice = [1, 2, 3, 4];
/// let _ = slice.array_chunks_mut::<N>();
/// ```
///
/// [`chunks_exact_mut`]: slice::chunks_exact_mut
#[unstable(feature = "array_chunks", issue = "74985")]
#[inline]
#[track_caller]
pub fn array_chunks_mut<const N: usize>(&mut self) -> ArrayChunksMut<'_, T, N> {
assert!(N != 0, "chunk size must be non-zero");
const {
assert!(N != 0, "chunk size must be non-zero");
}
ArrayChunksMut::new(self)
}

Expand All @@ -1287,11 +1356,6 @@ impl<T> [T] {
///
/// If `N` is greater than the size of the slice, it will return no windows.
///
/// # Panics
///
/// Panics if `N` is 0. This check will most probably get changed to a compile time
/// error before this method gets stabilized.
///
/// # Examples
///
/// ```
Expand All @@ -1304,12 +1368,22 @@ impl<T> [T] {
/// assert!(iter.next().is_none());
/// ```
///
/// It doesn't compile if window size is zero:
/// ```compile_fail
/// #![feature(array_windows)]
/// const N: usize = 0;
/// let slice = [0, 1, 2, 3];
/// let _ = slice.array_windows::<N>();
/// ```
///
/// [`windows`]: slice::windows
#[unstable(feature = "array_windows", issue = "75027")]
#[inline]
#[track_caller]
pub fn array_windows<const N: usize>(&self) -> ArrayWindows<'_, T, N> {
assert!(N != 0, "window size must be non-zero");
const {
assert!(N != 0, "window size must be non-zero");
}
ArrayWindows::new(self)
}

Expand Down

0 comments on commit 7bb2c00

Please sign in to comment.