From 7bb2c006d78b5fc593bdc2a679645e15f5e0080e Mon Sep 17 00:00:00 2001 From: AngelicosPhosphoros Date: Tue, 19 Jul 2022 17:35:20 +0300 Subject: [PATCH] Make `<[T]>::array_*` methods fail to compile on 0 len arrays 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::::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::::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: https://github.com/rust-lang/rust/issues/74985 https://github.com/rust-lang/rust/issues/75027 --- library/core/src/slice/mod.rs | 168 ++++++++++++++++++++++++---------- 1 file changed, 121 insertions(+), 47 deletions(-) diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 5ece1b78c0346..3dcf6fee104b5 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -972,7 +972,6 @@ impl [T] { /// /// This may only be called when /// - The slice splits exactly into `N`-element chunks (aka `self.len() % N == 0`). - /// - `N != 0`. /// /// # Examples /// @@ -990,14 +989,28 @@ impl [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::() + /// }; + /// ``` + /// #[unstable(feature = "slice_as_chunks", issue = "74985")] #[inline] #[must_use] pub const unsafe fn as_chunks_unchecked(&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", @@ -1014,11 +1027,6 @@ impl [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 /// /// ``` @@ -1039,16 +1047,28 @@ impl [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::(); + /// ``` + /// #[unstable(feature = "slice_as_chunks", issue = "74985")] #[inline] #[track_caller] #[must_use] pub const fn as_chunks(&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) } @@ -1071,16 +1091,28 @@ impl [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::(); + /// ``` + /// #[unstable(feature = "slice_as_chunks", issue = "74985")] #[inline] #[track_caller] #[must_use] pub const fn as_rchunks(&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) } @@ -1094,11 +1126,6 @@ impl [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 /// /// ``` @@ -1111,12 +1138,22 @@ impl [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::(); + /// ``` + /// /// [`chunks_exact`]: slice::chunks_exact #[unstable(feature = "array_chunks", issue = "74985")] #[inline] #[track_caller] pub fn array_chunks(&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) } @@ -1127,7 +1164,6 @@ impl [T] { /// /// This may only be called when /// - The slice splits exactly into `N`-element chunks (aka `self.len() % N == 0`). - /// - `N != 0`. /// /// # Examples /// @@ -1147,14 +1183,28 @@ impl [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::(); + /// }; + /// ``` + /// #[unstable(feature = "slice_as_chunks", issue = "74985")] #[inline] #[must_use] pub const unsafe fn as_chunks_unchecked_mut(&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", @@ -1171,11 +1221,6 @@ impl [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 /// /// ``` @@ -1191,16 +1236,28 @@ impl [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::(); + /// ``` + /// #[unstable(feature = "slice_as_chunks", issue = "74985")] #[inline] #[track_caller] #[must_use] pub const fn as_chunks_mut(&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) } @@ -1209,11 +1266,6 @@ impl [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 /// /// ``` @@ -1229,16 +1281,28 @@ impl [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::(); + /// ``` + /// #[unstable(feature = "slice_as_chunks", issue = "74985")] #[inline] #[track_caller] #[must_use] pub const fn as_rchunks_mut(&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) } @@ -1252,11 +1316,6 @@ impl [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 /// /// ``` @@ -1271,12 +1330,22 @@ impl [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::(); + /// ``` + /// /// [`chunks_exact_mut`]: slice::chunks_exact_mut #[unstable(feature = "array_chunks", issue = "74985")] #[inline] #[track_caller] pub fn array_chunks_mut(&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) } @@ -1287,11 +1356,6 @@ impl [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 /// /// ``` @@ -1304,12 +1368,22 @@ impl [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::(); + /// ``` + /// /// [`windows`]: slice::windows #[unstable(feature = "array_windows", issue = "75027")] #[inline] #[track_caller] pub fn array_windows(&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) }