about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2020-08-20 23:05:31 +0000
committerbors <bors@rust-lang.org>2020-08-20 23:05:31 +0000
commitd9d4d39612aae0b8398340bd83d592cafad8e4ec (patch)
treeba461f2cd99e0f09f35192da7848cbc8e2a3950c
parente15510ca33ea15c893b78710101c962b11459963 (diff)
parentdbad8c93680710e80c54cbaf8416821f7a5750c8 (diff)
downloadrust-d9d4d39612aae0b8398340bd83d592cafad8e4ec.tar.gz
rust-d9d4d39612aae0b8398340bd83d592cafad8e4ec.zip
Auto merge of #73565 - matthewjasper:core-min-spec, r=nagisa
Use min_specialization in libcore

Getting `TrustedRandomAccess` to work is the main interesting thing here.

- `get_unchecked` is now an unstable, hidden method on `Iterator`
- The contract for `TrustedRandomAccess` is made clearer in documentation
- Fixed a bug where `Debug` would create aliasing references when using the specialized zip impl
- Added tests for the side effects of `next_back` and `nth`.

closes #68536
-rw-r--r--library/core/src/iter/adapters/fuse.rs30
-rw-r--r--library/core/src/iter/adapters/mod.rs93
-rw-r--r--library/core/src/iter/adapters/zip.rs181
-rw-r--r--library/core/src/iter/traits/iterator.rs12
-rw-r--r--library/core/src/lib.rs2
-rw-r--r--library/core/src/slice/mod.rs256
-rw-r--r--library/core/src/str/mod.rs16
-rw-r--r--library/core/tests/iter.rs97
8 files changed, 487 insertions, 200 deletions
diff --git a/library/core/src/iter/adapters/fuse.rs b/library/core/src/iter/adapters/fuse.rs
index d2e2fc04a2b..94ba6f56476 100644
--- a/library/core/src/iter/adapters/fuse.rs
+++ b/library/core/src/iter/adapters/fuse.rs
@@ -1,7 +1,7 @@
 use crate::intrinsics;
-use crate::iter::{
-    DoubleEndedIterator, ExactSizeIterator, FusedIterator, Iterator, TrustedRandomAccess,
-};
+use crate::iter::adapters::zip::try_get_unchecked;
+use crate::iter::TrustedRandomAccess;
+use crate::iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator, Iterator};
 use crate::ops::Try;
 
 /// An iterator that yields `None` forever after the underlying iterator
@@ -114,6 +114,19 @@ where
     {
         FuseImpl::find(self, predicate)
     }
+
+    #[inline]
+    unsafe fn get_unchecked(&mut self, idx: usize) -> Self::Item
+    where
+        Self: TrustedRandomAccess,
+    {
+        match self.iter {
+            // SAFETY: the caller must uphold the contract for `Iterator::get_unchecked`.
+            Some(ref mut iter) => unsafe { try_get_unchecked(iter, idx) },
+            // SAFETY: the caller asserts there is an item at `i`, so we're not exhausted.
+            None => unsafe { intrinsics::unreachable() },
+        }
+    }
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -172,19 +185,12 @@ where
     }
 }
 
+#[doc(hidden)]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<I> TrustedRandomAccess for Fuse<I>
 where
     I: TrustedRandomAccess,
 {
-    unsafe fn get_unchecked(&mut self, i: usize) -> I::Item {
-        match self.iter {
-            // SAFETY: the caller must uphold the contract for `TrustedRandomAccess::get_unchecked`.
-            Some(ref mut iter) => unsafe { iter.get_unchecked(i) },
-            // SAFETY: the caller asserts there is an item at `i`, so we're not exhausted.
-            None => unsafe { intrinsics::unreachable() },
-        }
-    }
-
     fn may_have_side_effect() -> bool {
         I::may_have_side_effect()
     }
diff --git a/library/core/src/iter/adapters/mod.rs b/library/core/src/iter/adapters/mod.rs
index 133643a0c7f..9fcd137e1a6 100644
--- a/library/core/src/iter/adapters/mod.rs
+++ b/library/core/src/iter/adapters/mod.rs
@@ -15,6 +15,7 @@ pub use self::chain::Chain;
 #[stable(feature = "rust1", since = "1.0.0")]
 pub use self::flatten::{FlatMap, Flatten};
 pub use self::fuse::Fuse;
+use self::zip::try_get_unchecked;
 pub(crate) use self::zip::TrustedRandomAccess;
 pub use self::zip::Zip;
 
@@ -213,6 +214,15 @@ where
     fn count(self) -> usize {
         self.it.count()
     }
+
+    unsafe fn get_unchecked(&mut self, idx: usize) -> T
+    where
+        Self: TrustedRandomAccess,
+    {
+        // SAFETY: the caller must uphold the contract for
+        // `Iterator::get_unchecked`.
+        *unsafe { try_get_unchecked(&mut self.it, idx) }
+    }
 }
 
 #[stable(feature = "iter_copied", since = "1.36.0")]
@@ -266,16 +276,11 @@ where
 }
 
 #[doc(hidden)]
-unsafe impl<'a, I, T: 'a> TrustedRandomAccess for Copied<I>
+#[unstable(feature = "trusted_random_access", issue = "none")]
+unsafe impl<I> TrustedRandomAccess for Copied<I>
 where
-    I: TrustedRandomAccess<Item = &'a T>,
-    T: Copy,
+    I: TrustedRandomAccess,
 {
-    unsafe fn get_unchecked(&mut self, i: usize) -> Self::Item {
-        // SAFETY: the caller must uphold the contract for `TrustedRandomAccess::get_unchecked`.
-        unsafe { *self.it.get_unchecked(i) }
-    }
-
     #[inline]
     fn may_have_side_effect() -> bool {
         I::may_have_side_effect()
@@ -344,6 +349,15 @@ where
     {
         self.it.map(T::clone).fold(init, f)
     }
+
+    unsafe fn get_unchecked(&mut self, idx: usize) -> T
+    where
+        Self: TrustedRandomAccess,
+    {
+        // SAFETY: the caller must uphold the contract for
+        // `Iterator::get_unchecked`.
+        unsafe { try_get_unchecked(&mut self.it, idx).clone() }
+    }
 }
 
 #[stable(feature = "iter_cloned", since = "1.1.0")]
@@ -397,36 +411,14 @@ where
 }
 
 #[doc(hidden)]
-unsafe impl<'a, I, T: 'a> TrustedRandomAccess for Cloned<I>
+#[unstable(feature = "trusted_random_access", issue = "none")]
+unsafe impl<I> TrustedRandomAccess for Cloned<I>
 where
-    I: TrustedRandomAccess<Item = &'a T>,
-    T: Clone,
-{
-    default unsafe fn get_unchecked(&mut self, i: usize) -> Self::Item {
-        // SAFETY: the caller must uphold the contract for `TrustedRandomAccess::get_unchecked`.
-        unsafe { self.it.get_unchecked(i) }.clone()
-    }
-
-    #[inline]
-    default fn may_have_side_effect() -> bool {
-        true
-    }
-}
-
-#[doc(hidden)]
-unsafe impl<'a, I, T: 'a> TrustedRandomAccess for Cloned<I>
-where
-    I: TrustedRandomAccess<Item = &'a T>,
-    T: Copy,
+    I: TrustedRandomAccess,
 {
-    unsafe fn get_unchecked(&mut self, i: usize) -> Self::Item {
-        // SAFETY: the caller must uphold the contract for `TrustedRandomAccess::get_unchecked`.
-        unsafe { *self.it.get_unchecked(i) }
-    }
-
     #[inline]
     fn may_have_side_effect() -> bool {
-        I::may_have_side_effect()
+        true
     }
 }
 
@@ -872,6 +864,15 @@ where
     {
         self.iter.fold(init, map_fold(self.f, g))
     }
+
+    unsafe fn get_unchecked(&mut self, idx: usize) -> B
+    where
+        Self: TrustedRandomAccess,
+    {
+        // SAFETY: the caller must uphold the contract for
+        // `Iterator::get_unchecked`.
+        unsafe { (self.f)(try_get_unchecked(&mut self.iter, idx)) }
+    }
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -927,15 +928,11 @@ where
 }
 
 #[doc(hidden)]
-unsafe impl<B, I, F> TrustedRandomAccess for Map<I, F>
+#[unstable(feature = "trusted_random_access", issue = "none")]
+unsafe impl<I, F> TrustedRandomAccess for Map<I, F>
 where
     I: TrustedRandomAccess,
-    F: FnMut(I::Item) -> B,
 {
-    unsafe fn get_unchecked(&mut self, i: usize) -> Self::Item {
-        // SAFETY: the caller must uphold the contract for `TrustedRandomAccess::get_unchecked`.
-        (self.f)(unsafe { self.iter.get_unchecked(i) })
-    }
     #[inline]
     fn may_have_side_effect() -> bool {
         true
@@ -1306,6 +1303,16 @@ where
 
         self.iter.fold(init, enumerate(self.count, fold))
     }
+
+    unsafe fn get_unchecked(&mut self, idx: usize) -> <Self as Iterator>::Item
+    where
+        Self: TrustedRandomAccess,
+    {
+        // SAFETY: the caller must uphold the contract for
+        // `Iterator::get_unchecked`.
+        let value = unsafe { try_get_unchecked(&mut self.iter, idx) };
+        (Add::add(self.count, idx), value)
+    }
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -1391,15 +1398,11 @@ where
 }
 
 #[doc(hidden)]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<I> TrustedRandomAccess for Enumerate<I>
 where
     I: TrustedRandomAccess,
 {
-    unsafe fn get_unchecked(&mut self, i: usize) -> (usize, I::Item) {
-        // SAFETY: the caller must uphold the contract for `TrustedRandomAccess::get_unchecked`.
-        (self.count + i, unsafe { self.iter.get_unchecked(i) })
-    }
-
     fn may_have_side_effect() -> bool {
         I::may_have_side_effect()
     }
diff --git a/library/core/src/iter/adapters/zip.rs b/library/core/src/iter/adapters/zip.rs
index 985e6561665..6cb61896483 100644
--- a/library/core/src/iter/adapters/zip.rs
+++ b/library/core/src/iter/adapters/zip.rs
@@ -1,4 +1,5 @@
 use crate::cmp;
+use crate::fmt::{self, Debug};
 
 use super::super::{DoubleEndedIterator, ExactSizeIterator, FusedIterator, Iterator, TrustedLen};
 
@@ -9,7 +10,7 @@ use super::super::{DoubleEndedIterator, ExactSizeIterator, FusedIterator, Iterat
 ///
 /// [`zip`]: trait.Iterator.html#method.zip
 /// [`Iterator`]: trait.Iterator.html
-#[derive(Clone, Debug)]
+#[derive(Clone)]
 #[must_use = "iterators are lazy and do nothing unless consumed"]
 #[stable(feature = "rust1", since = "1.0.0")]
 pub struct Zip<A, B> {
@@ -56,6 +57,16 @@ where
     fn nth(&mut self, n: usize) -> Option<Self::Item> {
         ZipImpl::nth(self, n)
     }
+
+    #[inline]
+    unsafe fn get_unchecked(&mut self, idx: usize) -> Self::Item
+    where
+        Self: TrustedRandomAccess,
+    {
+        // SAFETY: `ZipImpl::get_unchecked` has same safety requirements as
+        // `Iterator::get_unchecked`.
+        unsafe { ZipImpl::get_unchecked(self, idx) }
+    }
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -82,6 +93,10 @@ trait ZipImpl<A, B> {
     where
         A: DoubleEndedIterator + ExactSizeIterator,
         B: DoubleEndedIterator + ExactSizeIterator;
+    // This has the same safety requirements as `Iterator::get_unchecked`
+    unsafe fn get_unchecked(&mut self, idx: usize) -> <Self as Iterator>::Item
+    where
+        Self: Iterator + TrustedRandomAccess;
 }
 
 // General Zip impl
@@ -156,16 +171,23 @@ where
 
         (lower, upper)
     }
+
+    default unsafe fn get_unchecked(&mut self, _idx: usize) -> <Self as Iterator>::Item
+    where
+        Self: TrustedRandomAccess,
+    {
+        unreachable!("Always specialized");
+    }
 }
 
 #[doc(hidden)]
 impl<A, B> ZipImpl<A, B> for Zip<A, B>
 where
-    A: TrustedRandomAccess,
-    B: TrustedRandomAccess,
+    A: TrustedRandomAccess + Iterator,
+    B: TrustedRandomAccess + Iterator,
 {
     fn new(a: A, b: B) -> Self {
-        let len = cmp::min(a.len(), b.len());
+        let len = cmp::min(a.size(), b.size());
         Zip { a, b, index: 0, len }
     }
 
@@ -176,7 +198,7 @@ where
             self.index += 1;
             // SAFETY: `i` is smaller than `self.len`, thus smaller than `self.a.len()` and `self.b.len()`
             unsafe { Some((self.a.get_unchecked(i), self.b.get_unchecked(i))) }
-        } else if A::may_have_side_effect() && self.index < self.a.len() {
+        } else if A::may_have_side_effect() && self.index < self.a.size() {
             // match the base implementation's potential side effects
             // SAFETY: we just checked that `self.index` < `self.a.len()`
             unsafe {
@@ -227,20 +249,26 @@ where
         A: DoubleEndedIterator + ExactSizeIterator,
         B: DoubleEndedIterator + ExactSizeIterator,
     {
-        // Adjust a, b to equal length
-        if A::may_have_side_effect() {
-            let sz = self.a.len();
-            if sz > self.len {
-                for _ in 0..sz - cmp::max(self.len, self.index) {
-                    self.a.next_back();
+        let a_side_effect = A::may_have_side_effect();
+        let b_side_effect = B::may_have_side_effect();
+        if a_side_effect || b_side_effect {
+            let sz_a = self.a.size();
+            let sz_b = self.b.size();
+            // Adjust a, b to equal length, make sure that only the first call
+            // of `next_back` does this, otherwise we will break the restriction
+            // on calls to `self.next_back()` after calling `get_unchecked()`.
+            if sz_a != sz_b {
+                let sz_a = self.a.size();
+                if a_side_effect && sz_a > self.len {
+                    for _ in 0..sz_a - cmp::max(self.len, self.index) {
+                        self.a.next_back();
+                    }
                 }
-            }
-        }
-        if B::may_have_side_effect() {
-            let sz = self.b.len();
-            if sz > self.len {
-                for _ in 0..sz - self.len {
-                    self.b.next_back();
+                let sz_b = self.b.size();
+                if b_side_effect && sz_b > self.len {
+                    for _ in 0..sz_b - self.len {
+                        self.b.next_back();
+                    }
                 }
             }
         }
@@ -254,6 +282,13 @@ where
             None
         }
     }
+
+    #[inline]
+    unsafe fn get_unchecked(&mut self, idx: usize) -> <Self as Iterator>::Item {
+        // SAFETY: the caller must uphold the contract for
+        // `Iterator::get_unchecked`.
+        unsafe { (self.a.get_unchecked(idx), self.b.get_unchecked(idx)) }
+    }
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -265,16 +300,12 @@ where
 }
 
 #[doc(hidden)]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<A, B> TrustedRandomAccess for Zip<A, B>
 where
     A: TrustedRandomAccess,
     B: TrustedRandomAccess,
 {
-    unsafe fn get_unchecked(&mut self, i: usize) -> (A::Item, B::Item) {
-        // SAFETY: the caller must uphold the contract for `TrustedRandomAccess::get_unchecked`.
-        unsafe { (self.a.get_unchecked(i), self.b.get_unchecked(i)) }
-    }
-
     fn may_have_side_effect() -> bool {
         A::may_have_side_effect() || B::may_have_side_effect()
     }
@@ -296,19 +327,109 @@ where
 {
 }
 
+#[stable(feature = "rust1", since = "1.0.0")]
+impl<A: Debug, B: Debug> Debug for Zip<A, B> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        ZipFmt::fmt(self, f)
+    }
+}
+
+trait ZipFmt<A, B> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
+}
+
+impl<A: Debug, B: Debug> ZipFmt<A, B> for Zip<A, B> {
+    default fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("Zip").field("a", &self.a).field("b", &self.b).finish()
+    }
+}
+
+impl<A: Debug + TrustedRandomAccess, B: Debug + TrustedRandomAccess> ZipFmt<A, B> for Zip<A, B> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        // It's *not safe* to call fmt on the contained iterators, since once
+        // we start iterating they're in strange, potentially unsafe, states.
+        f.debug_struct("Zip").finish()
+    }
+}
+
 /// An iterator whose items are random-accessible efficiently
 ///
 /// # Safety
 ///
-/// The iterator's .len() and size_hint() must be exact.
-/// `.len()` must be cheap to call.
+/// The iterator's `size_hint` must be exact and cheap to call.
+///
+/// `size` may not be overridden.
+///
+/// `<Self as Iterator>::get_unchecked` must be safe to call provided the
+/// following conditions are met.
 ///
-/// .get_unchecked() must return distinct mutable references for distinct
-/// indices (if applicable), and must return a valid reference if index is in
-/// 0..self.len().
-pub(crate) unsafe trait TrustedRandomAccess: ExactSizeIterator {
-    unsafe fn get_unchecked(&mut self, i: usize) -> Self::Item;
+/// 1. `0 <= idx` and `idx < self.size()`.
+/// 2. If `self: !Clone`, then `get_unchecked` is never called with the same
+///    index on `self` more than once.
+/// 3. After `self.get_unchecked(idx)` has been called then `next_back` will
+///    only be called at most `self.size() - idx - 1` times.
+/// 4. After `get_unchecked` is called, then only the following methods will be
+///    called on `self`:
+///     * `std::clone::Clone::clone`
+///     * `std::iter::Iterator::size_hint()`
+///     * `std::iter::Iterator::next_back()`
+///     * `std::iter::Iterator::get_unchecked()`
+///     * `std::iter::TrustedRandomAccess::size()`
+///
+/// Further, given that these conditions are met, it must guarantee that:
+///
+/// * It does not change the value returned from `size_hint`
+/// * It must be safe to call the methods listed above on `self` after calling
+///   `get_unchecked`, assuming that the required traits are implemented.
+/// * It must also be safe to drop `self` after calling `get_unchecked`.
+#[doc(hidden)]
+#[unstable(feature = "trusted_random_access", issue = "none")]
+#[rustc_specialization_trait]
+pub unsafe trait TrustedRandomAccess: Sized {
+    // Convenience method.
+    fn size(&self) -> usize
+    where
+        Self: Iterator,
+    {
+        self.size_hint().0
+    }
     /// Returns `true` if getting an iterator element may have
     /// side effects. Remember to take inner iterators into account.
     fn may_have_side_effect() -> bool;
 }
+
+/// Like `Iterator::get_unchecked`, but doesn't require the compiler to
+/// know that `U: TrustedRandomAccess`.
+///
+/// ## Safety
+///
+/// Same requirements calling `get_unchecked` directly.
+#[doc(hidden)]
+pub(in crate::iter::adapters) unsafe fn try_get_unchecked<I>(it: &mut I, idx: usize) -> I::Item
+where
+    I: Iterator,
+{
+    // SAFETY: the caller must uphold the contract for
+    // `Iterator::get_unchecked`.
+    unsafe { it.try_get_unchecked(idx) }
+}
+
+unsafe trait SpecTrustedRandomAccess: Iterator {
+    /// If `Self: TrustedRandomAccess`, it must be safe to call a
+    /// `Iterator::get_unchecked(self, index)`.
+    unsafe fn try_get_unchecked(&mut self, index: usize) -> Self::Item;
+}
+
+unsafe impl<I: Iterator> SpecTrustedRandomAccess for I {
+    default unsafe fn try_get_unchecked(&mut self, _: usize) -> Self::Item {
+        panic!("Should only be called on TrustedRandomAccess iterators");
+    }
+}
+
+unsafe impl<I: Iterator + TrustedRandomAccess> SpecTrustedRandomAccess for I {
+    unsafe fn try_get_unchecked(&mut self, index: usize) -> Self::Item {
+        // SAFETY: the caller must uphold the contract for
+        // `Iterator::get_unchecked`.
+        unsafe { self.get_unchecked(index) }
+    }
+}
diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs
index 81d8f27ec19..32e43ed42f3 100644
--- a/library/core/src/iter/traits/iterator.rs
+++ b/library/core/src/iter/traits/iterator.rs
@@ -6,6 +6,7 @@ use crate::cmp::{self, Ordering};
 use crate::ops::{Add, Try};
 
 use super::super::LoopState;
+use super::super::TrustedRandomAccess;
 use super::super::{Chain, Cloned, Copied, Cycle, Enumerate, Filter, FilterMap, Fuse};
 use super::super::{FlatMap, Flatten};
 use super::super::{FromIterator, Product, Sum, Zip};
@@ -3245,6 +3246,17 @@ pub trait Iterator {
     {
         self.map(f).is_sorted()
     }
+
+    /// See [TrustedRandomAccess]
+    #[inline]
+    #[doc(hidden)]
+    #[unstable(feature = "trusted_random_access", issue = "none")]
+    unsafe fn get_unchecked(&mut self, _idx: usize) -> Self::Item
+    where
+        Self: TrustedRandomAccess,
+    {
+        unreachable!("Always specialized");
+    }
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index 3838fcf74cc..19cd05de2a7 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -118,7 +118,7 @@
 #![feature(repr_simd, platform_intrinsics)]
 #![feature(rustc_attrs)]
 #![feature(simd_ffi)]
-#![feature(specialization)]
+#![feature(min_specialization)]
 #![feature(staged_api)]
 #![feature(std_internals)]
 #![feature(stmt_expr_attributes)]
diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs
index 59c536bcff9..0d97ddb29af 100644
--- a/library/core/src/slice/mod.rs
+++ b/library/core/src/slice/mod.rs
@@ -3647,21 +3647,21 @@ macro_rules! iterator {
         struct $name:ident -> $ptr:ty,
         $elem:ty,
         $raw_mut:tt,
-        {$( $mut_:tt )*},
+        {$( $mut_:tt )?},
         {$($extra:tt)*}
     ) => {
         // Returns the first element and moves the start of the iterator forwards by 1.
         // Greatly improves performance compared to an inlined function. The iterator
         // must not be empty.
         macro_rules! next_unchecked {
-            ($self: ident) => {& $( $mut_ )* *$self.post_inc_start(1)}
+            ($self: ident) => {& $( $mut_ )? *$self.post_inc_start(1)}
         }
 
         // Returns the last element and moves the end of the iterator backwards by 1.
         // Greatly improves performance compared to an inlined function. The iterator
         // must not be empty.
         macro_rules! next_back_unchecked {
-            ($self: ident) => {& $( $mut_ )* *$self.pre_dec_end(1)}
+            ($self: ident) => {& $( $mut_ )? *$self.pre_dec_end(1)}
         }
 
         // Shrinks the iterator when T is a ZST, by moving the end of the iterator
@@ -3921,6 +3921,21 @@ macro_rules! iterator {
                 None
             }
 
+            #[doc(hidden)]
+            unsafe fn get_unchecked(&mut self, idx: usize) -> Self::Item {
+                // SAFETY: the caller must guarantee that `i` is in bounds of
+                // the underlying slice, so `i` cannot overflow an `isize`, and
+                // the returned references is guaranteed to refer to an element
+                // of the slice and thus guaranteed to be valid.
+                //
+                // Also note that the caller also guarantees that we're never
+                // called with the same index again, and that no other methods
+                // that will access this subslice are called, so it is valid
+                // for the returned reference to be mutable in the case of
+                // `IterMut`
+                unsafe { & $( $mut_ )? * self.ptr.as_ptr().add(idx) }
+            }
+
             $($extra)*
         }
 
@@ -5005,6 +5020,15 @@ impl<'a, T> Iterator for Windows<'a, T> {
             Some(&self.v[start..])
         }
     }
+
+    #[doc(hidden)]
+    unsafe fn get_unchecked(&mut self, idx: usize) -> Self::Item {
+        // SAFETY: since the caller guarantees that `i` is in bounds,
+        // which means that `i` cannot overflow an `isize`, and the
+        // slice created by `from_raw_parts` is a subslice of `self.v`
+        // thus is guaranteed to be valid for the lifetime `'a` of `self.v`.
+        unsafe { from_raw_parts(self.v.as_ptr().add(idx), self.size) }
+    }
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -5044,14 +5068,8 @@ unsafe impl<T> TrustedLen for Windows<'_, T> {}
 impl<T> FusedIterator for Windows<'_, T> {}
 
 #[doc(hidden)]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<'a, T> TrustedRandomAccess for Windows<'a, T> {
-    unsafe fn get_unchecked(&mut self, i: usize) -> &'a [T] {
-        // SAFETY: since the caller guarantees that `i` is in bounds,
-        // which means that `i` cannot overflow an `isize`, and the
-        // slice created by `from_raw_parts` is a subslice of `self.v`
-        // thus is guaranteed to be valid for the lifetime `'a` of `self.v`.
-        unsafe { from_raw_parts(self.v.as_ptr().add(i), self.size) }
-    }
     fn may_have_side_effect() -> bool {
         false
     }
@@ -5141,6 +5159,23 @@ impl<'a, T> Iterator for Chunks<'a, T> {
             Some(&self.v[start..])
         }
     }
+
+    #[doc(hidden)]
+    unsafe fn get_unchecked(&mut self, idx: usize) -> Self::Item {
+        let start = idx * self.chunk_size;
+        let end = match start.checked_add(self.chunk_size) {
+            None => self.v.len(),
+            Some(end) => cmp::min(end, self.v.len()),
+        };
+        // SAFETY: the caller guarantees that `i` is in bounds,
+        // which means that `start` must be in bounds of the
+        // underlying `self.v` slice, and we made sure that `end`
+        // is also in bounds of `self.v`. Thus, `start` cannot overflow
+        // an `isize`, and the slice constructed by `from_raw_parts`
+        // is a subslice of `self.v` which is guaranteed to be valid
+        // for the lifetime `'a` of `self.v`.
+        unsafe { from_raw_parts(self.v.as_ptr().add(start), end - start) }
+    }
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -5187,22 +5222,8 @@ unsafe impl<T> TrustedLen for Chunks<'_, T> {}
 impl<T> FusedIterator for Chunks<'_, T> {}
 
 #[doc(hidden)]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<'a, T> TrustedRandomAccess for Chunks<'a, T> {
-    unsafe fn get_unchecked(&mut self, i: usize) -> &'a [T] {
-        let start = i * self.chunk_size;
-        let end = match start.checked_add(self.chunk_size) {
-            None => self.v.len(),
-            Some(end) => cmp::min(end, self.v.len()),
-        };
-        // SAFETY: the caller guarantees that `i` is in bounds,
-        // which means that `start` must be in bounds of the
-        // underlying `self.v` slice, and we made sure that `end`
-        // is also in bounds of `self.v`. Thus, `start` cannot overflow
-        // an `isize`, and the slice constructed by `from_raw_parts`
-        // is a subslice of `self.v` which is guaranteed to be valid
-        // for the lifetime `'a` of `self.v`.
-        unsafe { from_raw_parts(self.v.as_ptr().add(start), end - start) }
-    }
     fn may_have_side_effect() -> bool {
         false
     }
@@ -5287,6 +5308,22 @@ impl<'a, T> Iterator for ChunksMut<'a, T> {
             Some(&mut self.v[start..])
         }
     }
+
+    #[doc(hidden)]
+    unsafe fn get_unchecked(&mut self, idx: usize) -> Self::Item {
+        let start = idx * self.chunk_size;
+        let end = match start.checked_add(self.chunk_size) {
+            None => self.v.len(),
+            Some(end) => cmp::min(end, self.v.len()),
+        };
+        // SAFETY: see comments for `Chunks::get_unchecked`.
+        //
+        // Also note that the caller also guarantees that we're never called
+        // with the same index again, and that no other methods that will
+        // access this subslice are called, so it is valid for the returned
+        // slice to be mutable.
+        unsafe { from_raw_parts_mut(self.v.as_mut_ptr().add(start), end - start) }
+    }
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -5336,16 +5373,8 @@ unsafe impl<T> TrustedLen for ChunksMut<'_, T> {}
 impl<T> FusedIterator for ChunksMut<'_, T> {}
 
 #[doc(hidden)]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<'a, T> TrustedRandomAccess for ChunksMut<'a, T> {
-    unsafe fn get_unchecked(&mut self, i: usize) -> &'a mut [T] {
-        let start = i * self.chunk_size;
-        let end = match start.checked_add(self.chunk_size) {
-            None => self.v.len(),
-            Some(end) => cmp::min(end, self.v.len()),
-        };
-        // SAFETY: see comments for `Chunks::get_unchecked`.
-        unsafe { from_raw_parts_mut(self.v.as_mut_ptr().add(start), end - start) }
-    }
     fn may_have_side_effect() -> bool {
         false
     }
@@ -5432,6 +5461,13 @@ impl<'a, T> Iterator for ChunksExact<'a, T> {
     fn last(mut self) -> Option<Self::Item> {
         self.next_back()
     }
+
+    #[doc(hidden)]
+    unsafe fn get_unchecked(&mut self, idx: usize) -> Self::Item {
+        let start = idx * self.chunk_size;
+        // SAFETY: mostly identical to `Chunks::get_unchecked`.
+        unsafe { from_raw_parts(self.v.as_ptr().add(start), self.chunk_size) }
+    }
 }
 
 #[stable(feature = "chunks_exact", since = "1.31.0")]
@@ -5477,13 +5513,8 @@ unsafe impl<T> TrustedLen for ChunksExact<'_, T> {}
 impl<T> FusedIterator for ChunksExact<'_, T> {}
 
 #[doc(hidden)]
-#[stable(feature = "chunks_exact", since = "1.31.0")]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<'a, T> TrustedRandomAccess for ChunksExact<'a, T> {
-    unsafe fn get_unchecked(&mut self, i: usize) -> &'a [T] {
-        let start = i * self.chunk_size;
-        // SAFETY: mostly identical to `Chunks::get_unchecked`.
-        unsafe { from_raw_parts(self.v.as_ptr().add(start), self.chunk_size) }
-    }
     fn may_have_side_effect() -> bool {
         false
     }
@@ -5564,6 +5595,13 @@ impl<'a, T> Iterator for ChunksExactMut<'a, T> {
     fn last(mut self) -> Option<Self::Item> {
         self.next_back()
     }
+
+    #[doc(hidden)]
+    unsafe fn get_unchecked(&mut self, idx: usize) -> Self::Item {
+        let start = idx * self.chunk_size;
+        // SAFETY: see comments for `ChunksMut::get_unchecked`.
+        unsafe { from_raw_parts_mut(self.v.as_mut_ptr().add(start), self.chunk_size) }
+    }
 }
 
 #[stable(feature = "chunks_exact", since = "1.31.0")]
@@ -5612,13 +5650,8 @@ unsafe impl<T> TrustedLen for ChunksExactMut<'_, T> {}
 impl<T> FusedIterator for ChunksExactMut<'_, T> {}
 
 #[doc(hidden)]
-#[stable(feature = "chunks_exact", since = "1.31.0")]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<'a, T> TrustedRandomAccess for ChunksExactMut<'a, T> {
-    unsafe fn get_unchecked(&mut self, i: usize) -> &'a mut [T] {
-        let start = i * self.chunk_size;
-        // SAFETY: see comments for `ChunksExactMut::get_unchecked`.
-        unsafe { from_raw_parts_mut(self.v.as_mut_ptr().add(start), self.chunk_size) }
-    }
     fn may_have_side_effect() -> bool {
         false
     }
@@ -5689,6 +5722,12 @@ impl<'a, T, const N: usize> Iterator for ArrayChunks<'a, T, N> {
     fn last(self) -> Option<Self::Item> {
         self.iter.last()
     }
+
+    unsafe fn get_unchecked(&mut self, i: usize) -> &'a [T; N] {
+        // SAFETY: The safety guarantees of `get_unchecked` are transferred to
+        // the caller.
+        unsafe { self.iter.get_unchecked(i) }
+    }
 }
 
 #[unstable(feature = "array_chunks", issue = "74985")]
@@ -5720,11 +5759,6 @@ impl<T, const N: usize> FusedIterator for ArrayChunks<'_, T, N> {}
 #[doc(hidden)]
 #[unstable(feature = "array_chunks", issue = "74985")]
 unsafe impl<'a, T, const N: usize> TrustedRandomAccess for ArrayChunks<'a, T, N> {
-    unsafe fn get_unchecked(&mut self, i: usize) -> &'a [T; N] {
-        // SAFETY: The safety guarantees of `get_unchecked` are transferred to
-        // the caller.
-        unsafe { self.iter.get_unchecked(i) }
-    }
     fn may_have_side_effect() -> bool {
         false
     }
@@ -5817,6 +5851,17 @@ impl<'a, T> Iterator for RChunks<'a, T> {
             Some(&self.v[0..end])
         }
     }
+
+    #[doc(hidden)]
+    unsafe fn get_unchecked(&mut self, idx: usize) -> Self::Item {
+        let end = self.v.len() - idx * self.chunk_size;
+        let start = match end.checked_sub(self.chunk_size) {
+            None => 0,
+            Some(start) => start,
+        };
+        // SAFETY: mostly identical to `Chunks::get_unchecked`.
+        unsafe { from_raw_parts(self.v.as_ptr().add(start), end - start) }
+    }
 }
 
 #[stable(feature = "rchunks", since = "1.31.0")]
@@ -5862,17 +5907,8 @@ unsafe impl<T> TrustedLen for RChunks<'_, T> {}
 impl<T> FusedIterator for RChunks<'_, T> {}
 
 #[doc(hidden)]
-#[stable(feature = "rchunks", since = "1.31.0")]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<'a, T> TrustedRandomAccess for RChunks<'a, T> {
-    unsafe fn get_unchecked(&mut self, i: usize) -> &'a [T] {
-        let end = self.v.len() - i * self.chunk_size;
-        let start = match end.checked_sub(self.chunk_size) {
-            None => 0,
-            Some(start) => start,
-        };
-        // SAFETY: mostly identical to `Chunks::get_unchecked`.
-        unsafe { from_raw_parts(self.v.as_ptr().add(start), end - start) }
-    }
     fn may_have_side_effect() -> bool {
         false
     }
@@ -5961,6 +5997,17 @@ impl<'a, T> Iterator for RChunksMut<'a, T> {
             Some(&mut self.v[0..end])
         }
     }
+
+    #[doc(hidden)]
+    unsafe fn get_unchecked(&mut self, idx: usize) -> Self::Item {
+        let end = self.v.len() - idx * self.chunk_size;
+        let start = match end.checked_sub(self.chunk_size) {
+            None => 0,
+            Some(start) => start,
+        };
+        // SAFETY: see comments for `RChunks::get_unchecked` and `ChunksMut::get_unchecked`
+        unsafe { from_raw_parts_mut(self.v.as_mut_ptr().add(start), end - start) }
+    }
 }
 
 #[stable(feature = "rchunks", since = "1.31.0")]
@@ -6008,17 +6055,8 @@ unsafe impl<T> TrustedLen for RChunksMut<'_, T> {}
 impl<T> FusedIterator for RChunksMut<'_, T> {}
 
 #[doc(hidden)]
-#[stable(feature = "rchunks", since = "1.31.0")]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<'a, T> TrustedRandomAccess for RChunksMut<'a, T> {
-    unsafe fn get_unchecked(&mut self, i: usize) -> &'a mut [T] {
-        let end = self.v.len() - i * self.chunk_size;
-        let start = match end.checked_sub(self.chunk_size) {
-            None => 0,
-            Some(start) => start,
-        };
-        // SAFETY: see comments for `RChunks::get_unchecked`.
-        unsafe { from_raw_parts_mut(self.v.as_mut_ptr().add(start), end - start) }
-    }
     fn may_have_side_effect() -> bool {
         false
     }
@@ -6105,6 +6143,15 @@ impl<'a, T> Iterator for RChunksExact<'a, T> {
     fn last(mut self) -> Option<Self::Item> {
         self.next_back()
     }
+
+    #[doc(hidden)]
+    unsafe fn get_unchecked(&mut self, idx: usize) -> Self::Item {
+        let end = self.v.len() - idx * self.chunk_size;
+        let start = end - self.chunk_size;
+        // SAFETY:
+        // SAFETY: mostmy identical to `Chunks::get_unchecked`.
+        unsafe { from_raw_parts(self.v.as_ptr().add(start), self.chunk_size) }
+    }
 }
 
 #[stable(feature = "rchunks", since = "1.31.0")]
@@ -6153,14 +6200,8 @@ unsafe impl<T> TrustedLen for RChunksExact<'_, T> {}
 impl<T> FusedIterator for RChunksExact<'_, T> {}
 
 #[doc(hidden)]
-#[stable(feature = "rchunks", since = "1.31.0")]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<'a, T> TrustedRandomAccess for RChunksExact<'a, T> {
-    unsafe fn get_unchecked(&mut self, i: usize) -> &'a [T] {
-        let end = self.v.len() - i * self.chunk_size;
-        let start = end - self.chunk_size;
-        // SAFETY: mostmy identical to `Chunks::get_unchecked`.
-        unsafe { from_raw_parts(self.v.as_ptr().add(start), self.chunk_size) }
-    }
     fn may_have_side_effect() -> bool {
         false
     }
@@ -6243,6 +6284,14 @@ impl<'a, T> Iterator for RChunksExactMut<'a, T> {
     fn last(mut self) -> Option<Self::Item> {
         self.next_back()
     }
+
+    #[doc(hidden)]
+    unsafe fn get_unchecked(&mut self, idx: usize) -> Self::Item {
+        let end = self.v.len() - idx * self.chunk_size;
+        let start = end - self.chunk_size;
+        // SAFETY: see comments for `RChunksMut::get_unchecked`.
+        unsafe { from_raw_parts_mut(self.v.as_mut_ptr().add(start), self.chunk_size) }
+    }
 }
 
 #[stable(feature = "rchunks", since = "1.31.0")]
@@ -6293,14 +6342,8 @@ unsafe impl<T> TrustedLen for RChunksExactMut<'_, T> {}
 impl<T> FusedIterator for RChunksExactMut<'_, T> {}
 
 #[doc(hidden)]
-#[stable(feature = "rchunks", since = "1.31.0")]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<'a, T> TrustedRandomAccess for RChunksExactMut<'a, T> {
-    unsafe fn get_unchecked(&mut self, i: usize) -> &'a mut [T] {
-        let end = self.v.len() - i * self.chunk_size;
-        let start = end - self.chunk_size;
-        // SAFETY: see comments for `RChunksExact::get_unchecked`.
-        unsafe { from_raw_parts_mut(self.v.as_mut_ptr().add(start), self.chunk_size) }
-    }
     fn may_have_side_effect() -> bool {
         false
     }
@@ -6543,18 +6586,20 @@ where
 }
 
 // Use an equal-pointer optimization when types are `Eq`
-impl<A> SlicePartialEq<A> for [A]
+// We can't make `A` and `B` the same type because `min_specialization` won't
+// allow it.
+impl<A, B> SlicePartialEq<B> for [A]
 where
-    A: PartialEq<A> + Eq,
+    A: MarkerEq<B>,
 {
-    default fn equal(&self, other: &[A]) -> bool {
+    default fn equal(&self, other: &[B]) -> bool {
         if self.len() != other.len() {
             return false;
         }
 
         // While performance would suffer if `guaranteed_eq` just returned `false`
         // for all arguments, correctness and return value of this function are not affected.
-        if self.as_ptr().guaranteed_eq(other.as_ptr()) {
+        if self.as_ptr().guaranteed_eq(other.as_ptr() as *const A) {
             return true;
         }
 
@@ -6563,18 +6608,18 @@ where
 }
 
 // Use memcmp for bytewise equality when the types allow
-impl<A> SlicePartialEq<A> for [A]
+impl<A, B> SlicePartialEq<B> for [A]
 where
-    A: PartialEq<A> + BytewiseEquality,
+    A: BytewiseEquality<B>,
 {
-    fn equal(&self, other: &[A]) -> bool {
+    fn equal(&self, other: &[B]) -> bool {
         if self.len() != other.len() {
             return false;
         }
 
         // While performance would suffer if `guaranteed_eq` just returned `false`
         // for all arguments, correctness and return value of this function are not affected.
-        if self.as_ptr().guaranteed_eq(other.as_ptr()) {
+        if self.as_ptr().guaranteed_eq(other.as_ptr() as *const A) {
             return true;
         }
         // SAFETY: `self` and `other` are references and are thus guaranteed to be valid.
@@ -6631,6 +6676,7 @@ impl<A: AlwaysApplicableOrd> SlicePartialOrd for A {
     }
 }
 
+#[rustc_specialization_trait]
 trait AlwaysApplicableOrd: SliceOrd + Ord {}
 
 macro_rules! always_applicable_ord {
@@ -6695,15 +6741,22 @@ impl SliceOrd for u8 {
     }
 }
 
+// Hack to allow specializing on `Eq` even though `Eq` has a method.
+#[rustc_unsafe_specialization_marker]
+trait MarkerEq<T>: PartialEq<T> {}
+
+impl<T: Eq> MarkerEq<T> for T {}
+
 #[doc(hidden)]
 /// Trait implemented for types that can be compared for equality using
 /// their bytewise representation
-trait BytewiseEquality: Eq + Copy {}
+#[rustc_specialization_trait]
+trait BytewiseEquality<T>: MarkerEq<T> + Copy {}
 
 macro_rules! impl_marker_for {
     ($traitname:ident, $($ty:ty)*) => {
         $(
-            impl $traitname for $ty { }
+            impl $traitname<$ty> for $ty { }
         )*
     }
 }
@@ -6712,25 +6765,16 @@ impl_marker_for!(BytewiseEquality,
                  u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize char bool);
 
 #[doc(hidden)]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<'a, T> TrustedRandomAccess for Iter<'a, T> {
-    unsafe fn get_unchecked(&mut self, i: usize) -> &'a T {
-        // SAFETY: the caller must guarantee that `i` is in bounds
-        // of the underlying slice, so `i` cannot overflow an `isize`,
-        // and the returned references is guaranteed to refer to an element
-        // of the slice and thus guaranteed to be valid.
-        unsafe { &*self.ptr.as_ptr().add(i) }
-    }
     fn may_have_side_effect() -> bool {
         false
     }
 }
 
 #[doc(hidden)]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl<'a, T> TrustedRandomAccess for IterMut<'a, T> {
-    unsafe fn get_unchecked(&mut self, i: usize) -> &'a mut T {
-        // SAFETY: see comments for `Iter::get_unchecked`.
-        unsafe { &mut *self.ptr.as_ptr().add(i) }
-    }
     fn may_have_side_effect() -> bool {
         false
     }
diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs
index 4705c984bd4..7c1027f60ba 100644
--- a/library/core/src/str/mod.rs
+++ b/library/core/src/str/mod.rs
@@ -13,8 +13,9 @@ use self::pattern::{DoubleEndedSearcher, ReverseSearcher, Searcher};
 
 use crate::char;
 use crate::fmt::{self, Write};
+use crate::iter::TrustedRandomAccess;
 use crate::iter::{Chain, FlatMap, Flatten};
-use crate::iter::{Copied, Filter, FusedIterator, Map, TrustedLen, TrustedRandomAccess};
+use crate::iter::{Copied, Filter, FusedIterator, Map, TrustedLen};
 use crate::mem;
 use crate::ops::Try;
 use crate::option;
@@ -819,6 +820,13 @@ impl Iterator for Bytes<'_> {
     {
         self.0.rposition(predicate)
     }
+
+    #[inline]
+    unsafe fn get_unchecked(&mut self, idx: usize) -> u8 {
+        // SAFETY: the caller must uphold the safety contract
+        // for `Iterator::get_unchecked`.
+        unsafe { self.0.get_unchecked(idx) }
+    }
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -862,12 +870,8 @@ impl FusedIterator for Bytes<'_> {}
 unsafe impl TrustedLen for Bytes<'_> {}
 
 #[doc(hidden)]
+#[unstable(feature = "trusted_random_access", issue = "none")]
 unsafe impl TrustedRandomAccess for Bytes<'_> {
-    unsafe fn get_unchecked(&mut self, i: usize) -> u8 {
-        // SAFETY: the caller must uphold the safety contract
-        // for `TrustedRandomAccess::get_unchecked`.
-        unsafe { self.0.get_unchecked(i) }
-    }
     fn may_have_side_effect() -> bool {
         false
     }
diff --git a/library/core/tests/iter.rs b/library/core/tests/iter.rs
index 3b854b56c32..00e3972c42f 100644
--- a/library/core/tests/iter.rs
+++ b/library/core/tests/iter.rs
@@ -305,6 +305,103 @@ fn test_zip_nth_side_effects() {
 }
 
 #[test]
+fn test_zip_next_back_side_effects() {
+    let mut a = Vec::new();
+    let mut b = Vec::new();
+    let mut iter = [1, 2, 3, 4, 5, 6]
+        .iter()
+        .cloned()
+        .map(|n| {
+            a.push(n);
+            n * 10
+        })
+        .zip([2, 3, 4, 5, 6, 7, 8].iter().cloned().map(|n| {
+            b.push(n * 100);
+            n * 1000
+        }));
+
+    // The second iterator is one item longer, so `next_back` is called on it
+    // one more time.
+    assert_eq!(iter.next_back(), Some((60, 7000)));
+    assert_eq!(iter.next_back(), Some((50, 6000)));
+    assert_eq!(iter.next_back(), Some((40, 5000)));
+    assert_eq!(iter.next_back(), Some((30, 4000)));
+    assert_eq!(a, vec![6, 5, 4, 3]);
+    assert_eq!(b, vec![800, 700, 600, 500, 400]);
+}
+
+#[test]
+fn test_zip_nth_back_side_effects() {
+    let mut a = Vec::new();
+    let mut b = Vec::new();
+    let value = [1, 2, 3, 4, 5, 6]
+        .iter()
+        .cloned()
+        .map(|n| {
+            a.push(n);
+            n * 10
+        })
+        .zip([2, 3, 4, 5, 6, 7, 8].iter().cloned().map(|n| {
+            b.push(n * 100);
+            n * 1000
+        }))
+        .nth_back(3);
+    assert_eq!(value, Some((30, 4000)));
+    assert_eq!(a, vec![6, 5, 4, 3]);
+    assert_eq!(b, vec![800, 700, 600, 500, 400]);
+}
+
+#[test]
+fn test_zip_next_back_side_effects_exhausted() {
+    let mut a = Vec::new();
+    let mut b = Vec::new();
+    let mut iter = [1, 2, 3, 4, 5, 6]
+        .iter()
+        .cloned()
+        .map(|n| {
+            a.push(n);
+            n * 10
+        })
+        .zip([2, 3, 4].iter().cloned().map(|n| {
+            b.push(n * 100);
+            n * 1000
+        }));
+
+    iter.next();
+    iter.next();
+    iter.next();
+    iter.next();
+    assert_eq!(iter.next_back(), None);
+    assert_eq!(a, vec![1, 2, 3, 4, 6, 5]);
+    assert_eq!(b, vec![200, 300, 400]);
+}
+
+#[test]
+fn test_zip_nth_back_side_effects_exhausted() {
+    let mut a = Vec::new();
+    let mut b = Vec::new();
+    let mut iter = [1, 2, 3, 4, 5, 6]
+        .iter()
+        .cloned()
+        .map(|n| {
+            a.push(n);
+            n * 10
+        })
+        .zip([2, 3, 4].iter().cloned().map(|n| {
+            b.push(n * 100);
+            n * 1000
+        }));
+
+    iter.next();
+    iter.next();
+    iter.next();
+    iter.next();
+    assert_eq!(iter.nth_back(0), None);
+    assert_eq!(a, vec![1, 2, 3, 4, 6, 5]);
+    assert_eq!(b, vec![200, 300, 400]);
+}
+
+#[test]
 fn test_iterator_step_by() {
     // Identity
     let mut it = (0..).step_by(1).take(3);