diff --git a/src/liballoc/lib.rs b/src/liballoc/lib.rs
index 7aaa91ee10d97..a4cd5fd49e83a 100644
--- a/src/liballoc/lib.rs
+++ b/src/liballoc/lib.rs
@@ -75,6 +75,7 @@
 #![cfg_attr(not(test), feature(generator_trait))]
 #![cfg_attr(test, feature(test))]
 #![feature(allocator_api)]
+#![feature(array_chunks)]
 #![feature(allow_internal_unstable)]
 #![feature(arbitrary_self_types)]
 #![feature(box_patterns)]
diff --git a/src/liballoc/slice.rs b/src/liballoc/slice.rs
index 53477288b59ee..8d07d9284234a 100644
--- a/src/liballoc/slice.rs
+++ b/src/liballoc/slice.rs
@@ -95,6 +95,8 @@ use crate::borrow::ToOwned;
 use crate::boxed::Box;
 use crate::vec::Vec;
 
+#[unstable(feature = "array_chunks", issue = "none")]
+pub use core::slice::ArrayChunks;
 #[stable(feature = "slice_get_slice", since = "1.28.0")]
 pub use core::slice::SliceIndex;
 #[stable(feature = "from_ref", since = "1.28.0")]
diff --git a/src/libcore/slice/mod.rs b/src/libcore/slice/mod.rs
index 3386f83ec810f..b68eafcbe787a 100644
--- a/src/libcore/slice/mod.rs
+++ b/src/libcore/slice/mod.rs
@@ -674,7 +674,7 @@ impl<T> [T] {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[inline]
     pub fn windows(&self, size: usize) -> Windows<'_, T> {
-        assert!(size != 0);
+        assert_ne!(size, 0);
         Windows { v: self, size }
     }
 
@@ -708,7 +708,7 @@ impl<T> [T] {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[inline]
     pub fn chunks(&self, chunk_size: usize) -> Chunks<'_, T> {
-        assert!(chunk_size != 0);
+        assert_ne!(chunk_size, 0);
         Chunks { v: self, chunk_size }
     }
 
@@ -746,7 +746,7 @@ impl<T> [T] {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[inline]
     pub fn chunks_mut(&mut self, chunk_size: usize) -> ChunksMut<'_, T> {
-        assert!(chunk_size != 0);
+        assert_ne!(chunk_size, 0);
         ChunksMut { v: self, chunk_size }
     }
 
@@ -783,7 +783,7 @@ impl<T> [T] {
     #[stable(feature = "chunks_exact", since = "1.31.0")]
     #[inline]
     pub fn chunks_exact(&self, chunk_size: usize) -> ChunksExact<'_, T> {
-        assert!(chunk_size != 0);
+        assert_ne!(chunk_size, 0);
         let rem = self.len() % chunk_size;
         let len = self.len() - rem;
         let (fst, snd) = self.split_at(len);
@@ -828,13 +828,47 @@ impl<T> [T] {
     #[stable(feature = "chunks_exact", since = "1.31.0")]
     #[inline]
     pub fn chunks_exact_mut(&mut self, chunk_size: usize) -> ChunksExactMut<'_, T> {
-        assert!(chunk_size != 0);
+        assert_ne!(chunk_size, 0);
         let rem = self.len() % chunk_size;
         let len = self.len() - rem;
         let (fst, snd) = self.split_at_mut(len);
         ChunksExactMut { v: fst, rem: snd, chunk_size }
     }
 
+    /// Returns an iterator over `N` elements of the slice at a time, starting at the
+    /// beginning of the slice.
+    ///
+    /// The chunks are slices and do not overlap. If `N` does not divide the length of the
+    /// slice, then the last up to `N-1` elements will be omitted and can be retrieved
+    /// from the `remainder` function of the iterator.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `N` is 0.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(array_chunks)]
+    /// let slice = ['l', 'o', 'r', 'e', 'm'];
+    /// let mut iter = slice.array_chunks();
+    /// assert_eq!(iter.next().unwrap(), &['l', 'o']);
+    /// assert_eq!(iter.next().unwrap(), &['r', 'e']);
+    /// assert!(iter.next().is_none());
+    /// assert_eq!(iter.remainder(), &['m']);
+    /// ```
+    ///
+    /// [`chunks`]: #method.chunks
+    #[unstable(feature = "array_chunks", issue = "none")]
+    #[inline]
+    pub fn array_chunks<const N: usize>(&self) -> ArrayChunks<'_, T, N> {
+        assert_ne!(N, 0);
+        let rem = self.len() % N;
+        let len = self.len() - rem;
+        let (fst, snd) = self.split_at(len);
+        ArrayChunks { v: fst, rem: snd }
+    }
+
     /// Returns an iterator over `chunk_size` elements of the slice at a time, starting at the end
     /// of the slice.
     ///
@@ -5152,6 +5186,151 @@ unsafe impl<'a, T> TrustedRandomAccess for ChunksExactMut<'a, T> {
     }
 }
 
+/// An iterator over a slice in (non-overlapping) chunks (`N` elements at a
+/// time), starting at the beginning of the slice.
+///
+/// When the slice len is not evenly divided by the chunk size, the last
+/// up to `chunk_size-1` elements will be omitted but can be retrieved from
+/// the [`remainder`] function from the iterator.
+///
+/// This struct is created by the [`array_chunks`] method on [slices].
+///
+/// [`array_chunks`]: ../../std/primitive.slice.html#method.array_chunks
+/// [`remainder`]: ../../std/slice/struct.ArrayChunks.html#method.remainder
+/// [slices]: ../../std/primitive.slice.html
+#[derive(Debug)]
+#[unstable(feature = "array_chunks", issue = "none")]
+pub struct ArrayChunks<'a, T: 'a, const N: usize> {
+    v: &'a [T],
+    rem: &'a [T],
+}
+
+impl<'a, T, const N: usize> ArrayChunks<'a, T, N> {
+    /// Returns the remainder of the original slice that is not going to be
+    /// returned by the iterator. The returned slice has at most `chunk_size-1`
+    /// elements.
+    #[unstable(feature = "array_chunks", issue = "none")]
+    pub fn remainder(&self) -> &'a [T] {
+        self.rem
+    }
+}
+
+// FIXME(#26925) Remove in favor of `#[derive(Clone)]`
+#[unstable(feature = "array_chunks", issue = "none")]
+impl<T, const N: usize> Clone for ArrayChunks<'_, T, N> {
+    fn clone(&self) -> Self {
+        ArrayChunks { v: self.v, rem: self.rem }
+    }
+}
+
+#[unstable(feature = "array_chunks", issue = "none")]
+impl<'a, T, const N: usize> Iterator for ArrayChunks<'a, T, N> {
+    type Item = &'a [T; N];
+
+    #[inline]
+    fn next(&mut self) -> Option<&'a [T; N]> {
+        if self.v.len() < N {
+            None
+        } else {
+            let (fst, snd) = self.v.split_at(N);
+            self.v = snd;
+            // SAFETY: This is safe as fst is exactly N elements long.
+            let ptr = fst.as_ptr() as *const [T; N];
+            unsafe { Some(&*ptr) }
+        }
+    }
+
+    #[inline]
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let n = self.v.len() / N;
+        (n, Some(n))
+    }
+
+    #[inline]
+    fn count(self) -> usize {
+        self.len()
+    }
+
+    #[inline]
+    fn nth(&mut self, n: usize) -> Option<Self::Item> {
+        let (start, overflow) = n.overflowing_mul(N);
+        if start >= self.v.len() || overflow {
+            self.v = &[];
+            None
+        } else {
+            let (_, snd) = self.v.split_at(start);
+            self.v = snd;
+            self.next()
+        }
+    }
+
+    #[inline]
+    fn last(mut self) -> Option<Self::Item> {
+        self.next_back()
+    }
+}
+
+#[unstable(feature = "array_chunks", issue = "none")]
+impl<'a, T, const N: usize> DoubleEndedIterator for ArrayChunks<'a, T, N> {
+    #[inline]
+    fn next_back(&mut self) -> Option<&'a [T; N]> {
+        if self.v.len() < N {
+            None
+        } else {
+            let (fst, snd) = self.v.split_at(self.v.len() - N);
+            self.v = fst;
+            // SAFETY: This is safe as snd is exactly N elements long.
+            let ptr = snd.as_ptr() as *const [T; N];
+            unsafe { Some(&*ptr) }
+        }
+    }
+
+    #[inline]
+    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
+        let len = self.len();
+        if n >= len {
+            self.v = &[];
+            None
+        } else {
+            let start = (len - 1 - n) * N;
+            let end = start + N;
+            let nth_back = &self.v[start..end];
+            self.v = &self.v[..start];
+            // SAFETY: This is safe as snd is exactly N elements long.
+            let ptr = nth_back.as_ptr() as *const [T; N];
+            unsafe { Some(&*ptr) }
+        }
+    }
+}
+
+#[unstable(feature = "array_chunks", issue = "none")]
+impl<T, const N: usize> ExactSizeIterator for ArrayChunks<'_, T, N> {
+    fn is_empty(&self) -> bool {
+        self.v.is_empty()
+    }
+}
+
+#[unstable(feature = "trusted_len", issue = "37572")]
+unsafe impl<T, const N: usize> TrustedLen for ArrayChunks<'_, T, N> {}
+
+#[unstable(feature = "array_chunks", issue = "none")]
+impl<T, const N: usize> FusedIterator for ArrayChunks<'_, T, N> {}
+
+#[doc(hidden)]
+#[unstable(feature = "array_chunks", issue = "none")]
+unsafe impl<'a, T, const N: usize> TrustedRandomAccess for ArrayChunks<'a, T, N> {
+    unsafe fn get_unchecked(&mut self, i: usize) -> &'a [T; N] {
+        let start = i * N;
+        let segment = from_raw_parts(self.v.as_ptr().add(start), N);
+        // SAFETY: This is safe as segment is exactly N elements long.
+        let ptr = segment.as_ptr() as *const [T; N];
+        &*ptr
+    }
+    fn may_have_side_effect() -> bool {
+        false
+    }
+}
+
 /// An iterator over a slice in (non-overlapping) chunks (`chunk_size` elements at a
 /// time), starting at the end of the slice.
 ///
diff --git a/src/libcore/tests/lib.rs b/src/libcore/tests/lib.rs
index d636542d699f9..57261924fc0f8 100644
--- a/src/libcore/tests/lib.rs
+++ b/src/libcore/tests/lib.rs
@@ -1,4 +1,5 @@
 #![feature(alloc_layout_extra)]
+#![feature(array_chunks)]
 #![feature(bool_to_option)]
 #![feature(bound_cloned)]
 #![feature(box_syntax)]
diff --git a/src/libcore/tests/slice.rs b/src/libcore/tests/slice.rs
index 54a585415bce2..2585019c13ea6 100644
--- a/src/libcore/tests/slice.rs
+++ b/src/libcore/tests/slice.rs
@@ -433,6 +433,28 @@ fn test_chunks_exact_mut_zip() {
     assert_eq!(v1, [13, 14, 19, 20, 4]);
 }
 
+// FIXME(#71154)
+// FIXME(const_generics)
+// We can't yet use `v.array_chunks::<3>()`, so until either #71154 or a
+// different PR implementing const arguments in type dependent paths lands,
+// we can't yet test many uses.
+#[test]
+fn test_array_chunks_simple() {
+    let v: &[i32] = &[0, 1, 2, 3, 4, 5];
+    let mut c = <[i32]>::array_chunks(&v);
+    assert!(matches!(c.next(), Some(&[0, 1, 2])));
+    assert!(matches!(c.next(), Some(&[3, 4, 5])));
+    assert!(matches!(c.next(), None));
+
+    let v2: &[i32] = &[0, 1, 2, 3, 4];
+    let c2 = <[i32]>::array_chunks(&v2);
+    for &[a, b, c] in c2 {
+        assert_eq!(a, 0i32);
+        assert_eq!(b, 1i32);
+        assert_eq!(c, 2i32);
+    }
+}
+
 #[test]
 fn test_rchunks_count() {
     let v: &[i32] = &[0, 1, 2, 3, 4, 5];