Repository: rust-ndarray/ndarray
Pull request #939 (2021-03-13 14:23:42)

Update release notes in preparation for 0.15.0
The list of new features and improvements is pretty large for this release, this is looking good.

[Rendered preview](https://github.com/rust-ndarray/ndarray/blob/doc-0.15/RELEASES.md)


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 22686c5b..301d1b56 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -73,8 +73,6 @@ docs = ["approx", "serde", "rayon"]
 std = ["num-traits/std", "matrixmultiply/std"]
 rayon = ["rayon_", "std"]
 
-matrixmultiply-threading = ["matrixmultiply/threading"]
-
 [profile.release]
 [profile.bench]
 debug = true
diff --git a/README.rst b/README.rst
index 36b98598..8884ac9f 100644
--- a/README.rst
+++ b/README.rst
@@ -86,10 +86,6 @@ your `Cargo.toml`.
     Uses ``blas-src`` for pluggable backend, which needs to be configured
     separately (see below).
 
-- ``matrixmultiply-threading``
-
-  - Enable the ``threading`` feature in the matrixmultiply package
-
 How to use with cargo
 ---------------------
 
diff --git a/RELEASES.md b/RELEASES.md
index 2f410473..910378d0 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -4,28 +4,115 @@ Version 0.15.0 (Not released yet)
 New features
 ------------
 
-- Support for compiling ndarray as `no_std` (using core and alloc) by [@xd009642]
+- Support inserting new axes while slicing by [@jturner314]. This is an example:
 
-  https://github.com/rust-ndarray/ndarray/pull/861
+  ```rust
+  let view = arr.slice(s![.., -1, 2..;-1, NewAxis]);
+  ```
+
+  https://github.com/rust-ndarray/ndarray/pull/570
+
+- Support two-sided broadcasting in arithmetic operations with arrays by [@SparrowLii]
+
+  Note that this means that a new trait bound is required in some places when
+  mixing dimensionality types of arrays in arithmetic operations.
+
+  https://github.com/rust-ndarray/ndarray/pull/898
+
+- Support for compiling ndarray as `no_std` (using core and alloc) by
+  [@xd009642] and [@bluss]
+
+  https://github.com/rust-ndarray/ndarray/pull/861 <br>
+  https://github.com/rust-ndarray/ndarray/pull/889
 
 - New methods `.cell_view()` and `ArrayViewMut::into_cell_view` that enable
   new ways of working with array elements as if they were in Cells - setting
-  elements through shared views and broadcast views.
+  elements through shared views and broadcast views, by [@bluss].
 
   https://github.com/rust-ndarray/ndarray/pull/877
 
+- New methods `slice_each_axis/_mut/_inplace` that make it easier to slice
+  a dynamic number of axes in some situations, by [@jturner314]
+
+  https://github.com/rust-ndarray/ndarray/pull/913
+
+- New method `a.assign_to(b)` with the inverse argument order compared to the
+  existing `b.assign(a)` and some extra features like assigning into
+  uninitialized arrays, By [@bluss].
+
+  https://github.com/rust-ndarray/ndarray/pull/947
 
 Enhancements
 ------------
 
-- Fix `Zip` for the 0-dimensional case by [@jturner314]
+- New constructors `Array::from_iter` and `Array::from_vec` by [@bluss].
+  No new functionality, just that these constructors are avaiable without trait
+  imports.
 
-  https://github.com/rust-ndarray/ndarray/pull/862
+  https://github.com/rust-ndarray/ndarray/pull/921
+
+- Ndarray can now correctly determine that arrays can be contiguous, even if
+  they have negative strides, by [@SparrowLii]
+
+  https://github.com/rust-ndarray/ndarray/pull/885
+
+- `NdProducer::raw_dim` is now a documented method by [@jturner314]
+
+  https://github.com/rust-ndarray/ndarray/pull/918
+
+- `AxisDescription` is now a struct with field names, not a tuple struct by
+  [@jturner314]
+
+  https://github.com/rust-ndarray/ndarray/pull/915
+
+- Improvements to `map_inplace` by [@jturner314]
+
+  https://github.com/rust-ndarray/ndarray/pull/911
+
+- `.into_dimensionality` performance was improved for the `IxDyn` to `IxDyn`
+  case by [@bluss]
+
+  https://github.com/rust-ndarray/ndarray/pull/906
+
+- Improved performance for scalar + &array and &array + scalar operations by
+  [@jturner314]
+
+  https://github.com/rust-ndarray/ndarray/pull/890
+
+- Methods for array comparison `abs_diff_eq` and `relative_eq` are now
+  exposed as inherent methods too (no trait import needed), still under the approx
+  feature flag by [@bluss]
+
+  https://github.com/rust-ndarray/ndarray/pull/946
 
 API changes
 -----------
 
-- Removed deprecated methods by [@bluss]:
+- Changes to the slicing-related types and macro by [@jturner314] and [@bluss]:
+
+  - Remove the `Dimension::SliceArg` associated type, and add a new `SliceArg`
+    trait for this purpose.
+  - Change the return type of the `s![]` macro to an owned `SliceInfo` rather
+    than a reference.
+  - Replace the `SliceOrIndex` enum with `SliceInfoElem`, which has an
+    additional `NewAxis` variant and does not have a `step_by` method.
+  - Change the type parameters of `SliceInfo` in order to support the `NewAxis`
+    functionality and remove some tricky `unsafe` code.
+  - Mark the `SliceInfo::new` method as `unsafe`. The new implementations of
+    `TryFrom` can be used as a safe alternative.
+  - Remove the `AsRef<SliceInfo<[SliceOrIndex], D>> for SliceInfo<T, D>`
+    implementation. Add the similar `From<&'a SliceInfo<T, Din, Dout>> for
+    SliceInfo<&'a [SliceInfoElem], Din, Dout>` conversion as an alternative.
+  - Change the *expr* `;` *step* case in the `s![]` macro to error at compile
+    time if an unsupported type for *expr* is used, instead of panicking at
+    runtime.
+
+  https://github.com/rust-ndarray/ndarray/pull/570 <br>
+  https://github.com/rust-ndarray/ndarray/pull/940 <br>
+  https://github.com/rust-ndarray/ndarray/pull/943 <br>
+  https://github.com/rust-ndarray/ndarray/pull/945 <br>
+
+- Removed already deprecated methods by [@bluss]:
 
   - Remove deprecated `.all_close()` - use approx feature and methods like  `.abs_diff_eq` instead
   - Mark `.scalar_sum()` as deprecated - use `.sum()` instead
@@ -34,21 +121,106 @@ API changes
 
   https://github.com/rust-ndarray/ndarray/pull/874
 
-- Remove deprecated methods: rows, cols (for row and column count; the new
-  names are nrows and ncols) by [@bluss]
+- Remove already deprecated methods: rows, cols (for row and column count; the
+  new names are nrows and ncols) by [@bluss]
 
   https://github.com/rust-ndarray/ndarray/pull/872
 
-- Renamed methods (old names are now deprecated) by [@bluss]
+- Renamed `Zip` methods by [@bluss] and [@SparrowLii]:
+
+  - `apply` -> `for_each` 
+  - `apply_collect` -> `map_collect`
+  - `apply_collect_into` -> `map_collect_into`
+  - (`par_` prefixed methods renamed accordingly)
+
+  https://github.com/rust-ndarray/ndarray/pull/894 <br>
+  https://github.com/rust-ndarray/ndarray/pull/904 <br>
+
+- Deprecate `Array::uninitialized` and revamped its replacement by [@bluss]
+
+  Please use new new `Array::uninit` which is based on `MaybeUninit` (renamed
+  from `Array::maybe_uninit`, the old name is also deprecated).
+
+  https://github.com/rust-ndarray/ndarray/pull/902 <br>
+  https://github.com/rust-ndarray/ndarray/pull/876
+
+- Renamed methods (old names are now deprecated) by [@bluss] and [@jturner314]
 
   - `genrows/_mut` -> `rows/_mut`
   - `gencolumns/_mut` -> `columns/_mut`
+  - `stack_new_axis` -> `stack` (the new name already existed)
+  - `visit` -> `for_each`
 
-  https://github.com/rust-ndarray/ndarray/pull/872
+  https://github.com/rust-ndarray/ndarray/pull/872 <br>
+  https://github.com/rust-ndarray/ndarray/pull/937 <br>
+  https://github.com/rust-ndarray/ndarray/pull/907 <br>
+
+- `blas-src` dependency updated to 0.7.0 by [@bluss]
+
+  https://github.com/rust-ndarray/ndarray/pull/891
+
+- Updated `matrixmultiply` dependency to 0.3.0 by [@bluss]
+  and adding new feature flag `matrixmultiply-threading` to enable its threading
+
+  https://github.com/rust-ndarray/ndarray/pull/888 <br>
+  https://github.com/rust-ndarray/ndarray/pull/938 <br>
+ 
+
+Bug fixes
+---------
+
+- Fix `Zip::indexed` for the 0-dimensional case by [@jturner314]
+
+  https://github.com/rust-ndarray/ndarray/pull/862
+
+- Fix bug in layout computation that broke parallel collect to f-order
+  array in some circumstances by [@bluss]
+
+  https://github.com/rust-ndarray/ndarray/pull/900
+
+- Fix an unwanted panic in shape overflow checking by [@bluss]
+
+  https://github.com/rust-ndarray/ndarray/pull/855
+
+- Mark the `SliceInfo::new` method as `unsafe` due to the requirement that
+  `indices.as_ref()` always return the same value when called multiple times,
+  by [@bluss] and [@jturner314]
+
+  https://github.com/rust-ndarray/ndarray/pull/570
 
 Other changes
 -------------
 
+- Various improvements to tests and CI by [@jturner314]
+
+  https://github.com/rust-ndarray/ndarray/pull/934 <br>
+  https://github.com/rust-ndarray/ndarray/pull/924 <br>
+
+- The `sort-axis.rs` example file's implementation of sort was bugfixed and now
+  has tests, by [@dam5h] and [@bluss]
+
+  https://github.com/rust-ndarray/ndarray/pull/916 <br>
+  https://github.com/rust-ndarray/ndarray/pull/930
+
+- We now link to the #rust-sci room on matrix in the readme by [@jturner314]
+
+  https://github.com/rust-ndarray/ndarray/pull/619
+
+- Internal cleanup with builder-like methods for creating arrays by [@bluss]
+
+  https://github.com/rust-ndarray/ndarray/pull/908
+
+- Implementation fix of `.swap(i, j)` by [@bluss]
+
+  https://github.com/rust-ndarray/ndarray/pull/903
+
+- Minimum supported Rust version (MSRV) is Rust 1.49.
+
+  https://github.com/rust-ndarray/ndarray/pull/902
+
+- Minor improvements to docs by [@insideoutclub]
+
+  https://github.com/rust-ndarray/ndarray/pull/887
 
 
 Version 0.14.0 (2020-11-28)
@@ -1069,21 +1241,24 @@ Earlier releases
 [@bluss]: https://github.com/bluss
 [@jturner314]: https://github.com/jturner314
 [@LukeMathWalker]: https://github.com/LukeMathWalker
-[@max-sixty]: https://github.com/max-sixty
+[@acj]: https://github.com/acj
+[@andrei-papou]: https://github.com/andrei-papou
+[@dam5h]: https://github.com/dam5h
+[@d-dorazio]: https://github.com/d-dorazio
+[@Eijebong]: https://github.com/Eijebong
+[@insideoutclub]: https://github.com/insideoutclub
 [@JP-Ellis]: https://github.com/JP-Ellis
-[@sebasv]: https://github.com/sebasv
+[@lifuyang]: https://github.com/liufuyang
+[@max-sixty]: https://github.com/max-sixty
 [@mneumann]: https://github.com/mneumann
-[@termoshtt]: https://github.com/termoshtt
-[@rth]: https://github.com/rth
-[@nitsky]: https://github.com/nitsky
-[@d-dorazio]: https://github.com/d-dorazio
+[@mockersf]: https://github.com/mockersf
 [@nilgoyette]: https://github.com/nilgoyette
+[@nitsky]: https://github.com/nitsky
+[@rth]: https://github.com/rth
+[@sebasv]: https://github.com/sebasv
+[@SparrowLii]: https://github.com/SparrowLii
+[@termoshtt]: https://github.com/termoshtt
 [@TheLortex]: https://github.com/TheLortex
-[@mockersf]: https://github.com/mockersf
 [@viniciusd]: https://github.com/viniciusd
-[@lifuyang]: https://github.com/liufuyang
-[@acj]: https://github.com/acj
-[@Eijebong]: https://github.com/Eijebong
-[@andrei-papou]: https://github.com/andrei-papou
 [@xd009642]: https://github.com/xd009642
 [@Zuse64]: https://github.com/Zuse64
diff --git a/src/array_approx.rs b/src/array_approx.rs
index bb6804a2..a2e9c232 100644
--- a/src/array_approx.rs
+++ b/src/array_approx.rs
@@ -21,10 +21,9 @@ where
         if self.shape() != other.shape() {
             return false;
         }
-
         Zip::from(self)
             .and(other)
-            .all(move |a, b| A::abs_diff_eq(a, b, epsilon.clone()))
+            .all(|a, b| A::abs_diff_eq(a, b, epsilon.clone()))
     }
 }
 
@@ -50,10 +49,9 @@ where
         if self.shape() != other.shape() {
             return false;
         }
-
         Zip::from(self)
             .and(other)
-            .all(move |a, b| A::relative_eq(a, b, epsilon.clone(), max_relative.clone()))
+            .all(|a, b| A::relative_eq(a, b, epsilon.clone(), max_relative.clone()))
     }
 }
 
@@ -74,51 +72,12 @@ where
         if self.shape() != other.shape() {
             return false;
         }
-
         Zip::from(self)
             .and(other)
-            .all(move |a, b| A::ulps_eq(a, b, epsilon.clone(), max_ulps))
+            .all(|a, b| A::ulps_eq(a, b, epsilon.clone(), max_ulps))
     }
 }
 
-impl<A, S, D> ArrayBase<S, D>
-where
-    S: Data<Elem = A>,
-    D: Dimension,
-{
-    /// A test for equality that uses the elementwise absolute difference to compute the
-    /// approximate equality of two arrays.
-    ///
-    /// **Requires crate feature `"approx"`**
-    pub fn abs_diff_eq<S2>(&self, other: &ArrayBase<S2, D>, epsilon: A::Epsilon) -> bool
-    where
-        A: AbsDiffEq<S2::Elem>,
-        A::Epsilon: Clone,
-        S2: Data,
-    {
-        <Self as AbsDiffEq<_>>::abs_diff_eq(self, other, epsilon)
-    }
-
-    /// A test for equality that uses an elementwise relative comparison if the values are far
-    /// apart; and the absolute difference otherwise.
-    ///
-    /// **Requires crate feature `"approx"`**
-    pub fn relative_eq<S2>(
-        &self,
-        other: &ArrayBase<S2, D>,
-        epsilon: A::Epsilon,
-        max_relative: A::Epsilon,
-    ) -> bool
-    where
-        A: RelativeEq<S2::Elem>,
-        A::Epsilon: Clone,
-        S2: Data
-    {
-        <Self as RelativeEq<_>>::relative_eq(self, other, epsilon, max_relative)
-    }
-}
-
-
 #[cfg(test)]
 mod tests {
     use crate::prelude::*;
diff --git a/src/dimension/mod.rs b/src/dimension/mod.rs
index fb062513..243d5eee 100644
--- a/src/dimension/mod.rs
+++ b/src/dimension/mod.rs
@@ -8,7 +8,7 @@
 
 use crate::error::{from_kind, ErrorKind, ShapeError};
 use crate::slice::SliceArg;
-use crate::{Ix, Ixs, Slice, SliceInfoElem};
+use crate::{AxisSliceInfo, Ix, Ixs, Slice};
 use num_integer::div_floor;
 
 pub use self::axes::{axes_of, Axes, AxisDescription};
@@ -599,8 +599,8 @@ fn slice_min_max(axis_len: usize, slice: Slice) -> Option<(usize, usize)> {
 /// Returns `true` iff the slices intersect.
 pub fn slices_intersect<D: Dimension>(
     dim: &D,
-    indices1: impl SliceArg<D>,
-    indices2: impl SliceArg<D>,
+    indices1: &impl SliceArg<D>,
+    indices2: &impl SliceArg<D>,
 ) -> bool {
     debug_assert_eq!(indices1.in_ndim(), indices2.in_ndim());
     for (&axis_len, &si1, &si2) in izip!(
@@ -608,15 +608,15 @@ pub fn slices_intersect<D: Dimension>(
         indices1.as_ref().iter().filter(|si| !si.is_new_axis()),
         indices2.as_ref().iter().filter(|si| !si.is_new_axis()),
     ) {
-        // The slices do not intersect iff any pair of `SliceInfoElem` does not intersect.
+        // The slices do not intersect iff any pair of `AxisSliceInfo` does not intersect.
         match (si1, si2) {
             (
-                SliceInfoElem::Slice {
+                AxisSliceInfo::Slice {
                     start: start1,
                     end: end1,
                     step: step1,
                 },
-                SliceInfoElem::Slice {
+                AxisSliceInfo::Slice {
                     start: start2,
                     end: end2,
                     step: step2,
@@ -637,8 +637,8 @@ pub fn slices_intersect<D: Dimension>(
                     return false;
                 }
             }
-            (SliceInfoElem::Slice { start, end, step }, SliceInfoElem::Index(ind))
-            | (SliceInfoElem::Index(ind), SliceInfoElem::Slice { start, end, step }) => {
+            (AxisSliceInfo::Slice { start, end, step }, AxisSliceInfo::Index(ind))
+            | (AxisSliceInfo::Index(ind), AxisSliceInfo::Slice { start, end, step }) => {
                 let ind = abs_index(axis_len, ind);
                 let (min, max) = match slice_min_max(axis_len, Slice::new(start, end, step)) {
                     Some(m) => m,
@@ -648,14 +648,14 @@ pub fn slices_intersect<D: Dimension>(
                     return false;
                 }
             }
-            (SliceInfoElem::Index(ind1), SliceInfoElem::Index(ind2)) => {
+            (AxisSliceInfo::Index(ind1), AxisSliceInfo::Index(ind2)) => {
                 let ind1 = abs_index(axis_len, ind1);
                 let ind2 = abs_index(axis_len, ind2);
                 if ind1 != ind2 {
                     return false;
                 }
             }
-            (SliceInfoElem::NewAxis, _) | (_, SliceInfoElem::NewAxis) => unreachable!(),
+            (AxisSliceInfo::NewAxis, _) | (_, AxisSliceInfo::NewAxis) => unreachable!(),
         }
     }
     true
diff --git a/src/impl_constructors.rs b/src/impl_constructors.rs
index d082a5ce..63688ea7 100644
--- a/src/impl_constructors.rs
+++ b/src/impl_constructors.rs
@@ -531,14 +531,30 @@ where
     ///     // two first columns in b are two last in a
     ///     // rest of columns in b are the initial columns in a
     ///
-    ///     a.slice(s![.., -2..]).assign_to(b.slice_mut(s![.., ..2]));
-    ///     a.slice(s![.., 2..]).assign_to(b.slice_mut(s![.., ..-2]));
+    ///     assign_to(a.slice(s![.., -2..]), b.slice_mut(s![.., ..2]));
+    ///     assign_to(a.slice(s![.., 2..]), b.slice_mut(s![.., ..-2]));
     ///
     ///     // Now we can promise that `b` is safe to use with all operations
     ///     unsafe {
     ///         b.assume_init()
     ///     }
     /// }
+    ///
+    /// use ndarray::{IntoNdProducer, AssignElem};
+    ///
+    /// // This function clones elements from the first input to the second;
+    /// // the two producers must have the same shape
+    /// fn assign_to<'a, P1, P2, A>(from: P1, to: P2)
+    ///     where P1: IntoNdProducer<Item = &'a A>,
+    ///           P2: IntoNdProducer<Dim = P1::Dim>,
+    ///           P2::Item: AssignElem<A>,
+    ///           A: Clone + 'a
+    /// {
+    ///     Zip::from(from)
+    ///         .map_assign_into(to, A::clone);
+    /// }
+    ///
+    /// # shift_by_two(&Array2::zeros((8, 8)));
     /// ```
     pub fn uninit<Sh>(shape: Sh) -> ArrayBase<S::MaybeUninit, D>
     where
diff --git a/src/impl_methods.rs b/src/impl_methods.rs
index 4045f2b5..d039d68a 100644
--- a/src/impl_methods.rs
+++ b/src/impl_methods.rs
@@ -15,7 +15,6 @@ use rawpointer::PointerExt;
 use crate::imp_prelude::*;
 
 use crate::{arraytraits, DimMax};
-use crate::argument_traits::AssignElem;
 use crate::dimension;
 use crate::dimension::IntoDimension;
 use crate::dimension::{
@@ -26,7 +25,7 @@ use crate::dimension::broadcast::co_broadcast;
 use crate::error::{self, ErrorKind, ShapeError, from_kind};
 use crate::math_cell::MathCell;
 use crate::itertools::zip;
-use crate::zip::{IntoNdProducer, Zip};
+use crate::zip::Zip;
 use crate::AxisDescription;
 
 use crate::iter::{
@@ -35,7 +34,7 @@ use crate::iter::{
 };
 use crate::slice::{MultiSliceArg, SliceArg};
 use crate::stacking::concatenate;
-use crate::{NdIndex, Slice, SliceInfoElem};
+use crate::{AxisSliceInfo, NdIndex, Slice};
 
 /// # Methods For All Array Types
 impl<A, S, D> ArrayBase<S, D>
@@ -339,9 +338,9 @@ where
     ///
     /// **Panics** if an index is out of bounds or step size is zero.<br>
     /// (**Panics** if `D` is `IxDyn` and `info` does not match the number of array axes.)
-    pub fn slice<I>(&self, info: I) -> ArrayView<'_, A, I::OutDim>
+    pub fn slice<I>(&self, info: &I) -> ArrayView<'_, A, I::OutDim>
     where
-        I: SliceArg<D>,
+        I: SliceArg<D> + ?Sized,
         S: Data,
     {
         self.view().slice_move(info)
@@ -354,9 +353,9 @@ where
     ///
     /// **Panics** if an index is out of bounds or step size is zero.<br>
     /// (**Panics** if `D` is `IxDyn` and `info` does not match the number of array axes.)
-    pub fn slice_mut<I>(&mut self, info: I) -> ArrayViewMut<'_, A, I::OutDim>
+    pub fn slice_mut<I>(&mut self, info: &I) -> ArrayViewMut<'_, A, I::OutDim>
     where
-        I: SliceArg<D>,
+        I: SliceArg<D> + ?Sized,
         S: DataMut,
     {
         self.view_mut().slice_move(info)
@@ -400,9 +399,9 @@ where
     ///
     /// **Panics** if an index is out of bounds or step size is zero.<br>
     /// (**Panics** if `D` is `IxDyn` and `info` does not match the number of array axes.)
-    pub fn slice_move<I>(mut self, info: I) -> ArrayBase<S, I::OutDim>
+    pub fn slice_move<I>(mut self, info: &I) -> ArrayBase<S, I::OutDim>
     where
-        I: SliceArg<D>,
+        I: SliceArg<D> + ?Sized,
     {
         assert_eq!(
             info.in_ndim(),
@@ -416,7 +415,7 @@ where
         let mut old_axis = 0;
         let mut new_axis = 0;
         info.as_ref().iter().for_each(|&ax_info| match ax_info {
-            SliceInfoElem::Slice { start, end, step } => {
+            AxisSliceInfo::Slice { start, end, step } => {
                 // Slice the axis in-place to update the `dim`, `strides`, and `ptr`.
                 self.slice_axis_inplace(Axis(old_axis), Slice { start, end, step });
                 // Copy the sliced dim and stride to corresponding axis.
@@ -425,7 +424,7 @@ where
                 old_axis += 1;
                 new_axis += 1;
             }
-            SliceInfoElem::Index(index) => {
+            AxisSliceInfo::Index(index) => {
                 // Collapse the axis in-place to update the `ptr`.
                 let i_usize = abs_index(self.len_of(Axis(old_axis)), index);
                 self.collapse_axis(Axis(old_axis), i_usize);
@@ -435,7 +434,7 @@ where
                 // is zero length.
                 old_axis += 1;
             }
-            SliceInfoElem::NewAxis => {
+            AxisSliceInfo::NewAxis => {
                 // Set the dim and stride of the new axis.
                 new_dim[new_axis] = 1;
                 new_strides[new_axis] = 0;
@@ -466,12 +465,12 @@ where
     ///
     /// - if an index is out of bounds
     /// - if a step size is zero
-    /// - if [`SliceInfoElem::NewAxis`] is in `info`, e.g. if [`NewAxis`] was
+    /// - if [`AxisSliceInfo::NewAxis`] is in `info`, e.g. if [`NewAxis`] was
     ///   used in the [`s!`] macro
     /// - if `D` is `IxDyn` and `info` does not match the number of array axes
-    pub fn slice_collapse<I>(&mut self, info: I)
+    pub fn slice_collapse<I>(&mut self, info: &I)
     where
-        I: SliceArg<D>,
+        I: SliceArg<D> + ?Sized,
     {
         assert_eq!(
             info.in_ndim(),
@@ -480,16 +479,16 @@ where
         );
         let mut axis = 0;
         info.as_ref().iter().for_each(|&ax_info| match ax_info {
-                SliceInfoElem::Slice { start, end, step } => {
+                AxisSliceInfo::Slice { start, end, step } => {
                     self.slice_axis_inplace(Axis(axis), Slice { start, end, step });
                     axis += 1;
                 }
-                SliceInfoElem::Index(index) => {
+                AxisSliceInfo::Index(index) => {
                     let i_usize = abs_index(self.len_of(Axis(axis)), index);
                     self.collapse_axis(Axis(axis), i_usize);
                     axis += 1;
                 }
-                SliceInfoElem::NewAxis => panic!("`slice_collapse` does not support `NewAxis`."),
+                AxisSliceInfo::NewAxis => panic!("`slice_collapse` does not support `NewAxis`."),
             });
         debug_assert_eq!(axis, self.ndim());
     }
@@ -2050,23 +2049,6 @@ where
         self.zip_mut_with(rhs, |x, y| *x = y.clone());
     }
 
-    /// Perform an elementwise assigment of values cloned from `self` into array or producer `to`.
-    ///
-    /// The destination `to` can be another array or a producer of assignable elements.
-    /// [`AssignElem`] determines how elements are assigned.
-    ///
-    /// **Panics** if shapes disagree.
-    pub fn assign_to<P>(&self, to: P)
-    where
-        S: Data,
-        P: IntoNdProducer<Dim = D>,
-        P::Item: AssignElem<A>,
-        A: Clone,
-    {
-        Zip::from(self)
-            .map_assign_into(to, A::clone);
-    }
-
     /// Perform an elementwise assigment to `self` from element `x`.
     pub fn fill(&mut self, x: A)
     where
diff --git a/src/lib.rs b/src/lib.rs
index 9ec666c1..f48da4b3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -87,8 +87,6 @@
 //!   - Enable transparent BLAS support for matrix multiplication.
 //!     Uses ``blas-src`` for pluggable backend, which needs to be configured
 //!     separately (see the README).
-//! - `matrixmultiply-threading`
-//!   - Enable the ``threading`` feature in the matrixmultiply package
 //!
 //! ## Documentation
 //!
@@ -144,7 +142,7 @@ pub use crate::dimension::NdIndex;
 pub use crate::error::{ErrorKind, ShapeError};
 pub use crate::indexes::{indices, indices_of};
 pub use crate::slice::{
-    MultiSliceArg, NewAxis, Slice, SliceArg, SliceInfo, SliceInfoElem, SliceNextDim,
+    AxisSliceInfo, MultiSliceArg, NewAxis, Slice, SliceArg, SliceInfo, SliceNextDim,
 };
 
 use crate::iterators::Baseiter;
@@ -170,6 +168,8 @@ mod aliases;
 #[macro_use]
 mod itertools;
 mod argument_traits;
+#[cfg(feature = "approx")]
+mod array_approx;
 #[cfg(feature = "serde")]
 mod array_serde;
 mod arrayformat;
@@ -206,6 +206,7 @@ mod shape_builder;
 mod slice;
 mod split_at;
 mod stacking;
+mod traversal_utils;
 #[macro_use]
 mod zip;
 
@@ -491,8 +492,9 @@ pub type Ixs = isize;
 ///
 /// The slicing argument can be passed using the macro [`s![]`](macro.s!.html),
 /// which will be used in all examples. (The explicit form is an instance of
-/// [`SliceInfo`] or another type which implements [`SliceArg`]; see their docs
-/// for more information.)
+/// [`&SliceInfo`]; see its docs for more information.)
+///
+/// [`&SliceInfo`]: struct.SliceInfo.html
 ///
 /// If a range is used, the axis is preserved. If an index is used, that index
 /// is selected and the axis is removed; this selects a subview. See
@@ -508,7 +510,7 @@ pub type Ixs = isize;
 /// [`NewAxis`]: struct.NewAxis.html
 ///
 /// When slicing arrays with generic dimensionality, creating an instance of
-/// [`SliceInfo`] to pass to the multi-axis slicing methods like [`.slice()`]
+/// [`&SliceInfo`] to pass to the multi-axis slicing methods like [`.slice()`]
 /// is awkward. In these cases, it's usually more convenient to use
 /// [`.slice_each_axis()`]/[`.slice_each_axis_mut()`]/[`.slice_each_axis_inplace()`]
 /// or to create a view and then slice individual axes of the view using
@@ -1518,9 +1520,6 @@ impl<'a, A> CowRepr<'a, A> {
     }
 }
 
-// NOTE: The order of modules decides in which order methods on the type ArrayBase
-// (mainly mentioning that as the most relevant type) show up in the documentation.
-// Consider the doc effect of ordering modules here.
 mod impl_clone;
 
 mod impl_internal_constructors;
@@ -1614,9 +1613,6 @@ pub mod linalg;
 mod impl_ops;
 pub use crate::impl_ops::ScalarOperand;
 
-#[cfg(feature = "approx")]
-mod array_approx;
-
 // Array view methods
 mod impl_views;
 
diff --git a/src/slice.rs b/src/slice.rs
index 3c554a5c..43785555 100644
--- a/src/slice.rs
+++ b/src/slice.rs
@@ -78,33 +78,33 @@ pub struct NewAxis;
 /// A slice (range with step), an index, or a new axis token.
 ///
 /// See also the [`s![]`](macro.s!.html) macro for a convenient way to create a
-/// `SliceInfo<[SliceInfoElem; n], Din, Dout>`.
+/// `&SliceInfo<[AxisSliceInfo; n], Din, Dout>`.
 ///
 /// ## Examples
 ///
-/// `SliceInfoElem::Index(a)` is the index `a`. It can also be created with
-/// `SliceInfoElem::from(a)`. The Python equivalent is `[a]`. The macro
+/// `AxisSliceInfo::Index(a)` is the index `a`. It can also be created with
+/// `AxisSliceInfo::from(a)`. The Python equivalent is `[a]`. The macro
 /// equivalent is `s![a]`.
 ///
-/// `SliceInfoElem::Slice { start: 0, end: None, step: 1 }` is the full range
-/// of an axis. It can also be created with `SliceInfoElem::from(..)`. The
+/// `AxisSliceInfo::Slice { start: 0, end: None, step: 1 }` is the full range
+/// of an axis. It can also be created with `AxisSliceInfo::from(..)`. The
 /// Python equivalent is `[:]`. The macro equivalent is `s![..]`.
 ///
-/// `SliceInfoElem::Slice { start: a, end: Some(b), step: 2 }` is every second
+/// `AxisSliceInfo::Slice { start: a, end: Some(b), step: 2 }` is every second
 /// element from `a` until `b`. It can also be created with
-/// `SliceInfoElem::from(Slice::from(a..b).step_by(2))`. The Python equivalent
-/// is `[a:b:2]`. The macro equivalent is `s![a..b;2]`.
+/// `AxisSliceInfo::from(a..b).step_by(2)`. The Python equivalent is `[a:b:2]`.
+/// The macro equivalent is `s![a..b;2]`.
 ///
-/// `SliceInfoElem::Slice { start: a, end: None, step: -1 }` is every element,
+/// `AxisSliceInfo::Slice { start: a, end: None, step: -1 }` is every element,
 /// from `a` until the end, in reverse order. It can also be created with
-/// `SliceInfoElem::from(Slice::from(a..).step_by(-1))`. The Python equivalent
-/// is `[a::-1]`. The macro equivalent is `s![a..;-1]`.
+/// `AxisSliceInfo::from(a..).step_by(-1)`. The Python equivalent is `[a::-1]`.
+/// The macro equivalent is `s![a..;-1]`.
 ///
-/// `SliceInfoElem::NewAxis` is a new axis of length 1. It can also be created
-/// with `SliceInfoElem::from(NewAxis)`. The Python equivalent is
+/// `AxisSliceInfo::NewAxis` is a new axis of length 1. It can also be created
+/// with `AxisSliceInfo::from(NewAxis)`. The Python equivalent is
 /// `[np.newaxis]`. The macro equivalent is `s![NewAxis]`.
 #[derive(Debug, PartialEq, Eq, Hash)]
-pub enum SliceInfoElem {
+pub enum AxisSliceInfo {
     /// A range with step size. `end` is an exclusive index. Negative `begin`
     /// or `end` indexes are counted from the back of the axis. If `end` is
     /// `None`, the slice extends to the end of the axis.
@@ -119,30 +119,54 @@ pub enum SliceInfoElem {
     NewAxis,
 }
 
-copy_and_clone! {SliceInfoElem}
+copy_and_clone! {AxisSliceInfo}
 
-impl SliceInfoElem {
+impl AxisSliceInfo {
     /// Returns `true` if `self` is a `Slice` value.
     pub fn is_slice(&self) -> bool {
-        matches!(self, SliceInfoElem::Slice { .. })
+        matches!(self, AxisSliceInfo::Slice { .. })
     }
 
     /// Returns `true` if `self` is an `Index` value.
     pub fn is_index(&self) -> bool {
-        matches!(self, SliceInfoElem::Index(_))
+        matches!(self, AxisSliceInfo::Index(_))
     }
 
     /// Returns `true` if `self` is a `NewAxis` value.
     pub fn is_new_axis(&self) -> bool {
-        matches!(self, SliceInfoElem::NewAxis)
+        matches!(self, AxisSliceInfo::NewAxis)
+    }
+
+    /// Returns a new `AxisSliceInfo` with the given step size (multiplied with
+    /// the previous step size).
+    ///
+    /// `step` must be nonzero.
+    /// (This method checks with a debug assertion that `step` is not zero.)
+    ///
+    /// **Panics** if `self` is not the `AxisSliceInfo::Slice` variant.
+    #[inline]
+    pub fn step_by(self, step: isize) -> Self {
+        debug_assert_ne!(step, 0, "AxisSliceInfo::step_by: step must be nonzero");
+        match self {
+            AxisSliceInfo::Slice {
+                start,
+                end,
+                step: orig_step,
+            } => AxisSliceInfo::Slice {
+                start,
+                end,
+                step: orig_step * step,
+            },
+            _ => panic!("AxisSliceInfo::step_by: `self` must be the `Slice` variant"),
+        }
     }
 }
 
-impl fmt::Display for SliceInfoElem {
+impl fmt::Display for AxisSliceInfo {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match *self {
-            SliceInfoElem::Index(index) => write!(f, "{}", index)?,
-            SliceInfoElem::Slice { start, end, step } => {
+            AxisSliceInfo::Index(index) => write!(f, "{}", index)?,
+            AxisSliceInfo::Slice { start, end, step } => {
                 if start != 0 {
                     write!(f, "{}", start)?;
                 }
@@ -154,7 +178,7 @@ impl fmt::Display for SliceInfoElem {
                     write!(f, ";{}", step)?;
                 }
             }
-            SliceInfoElem::NewAxis => write!(f, stringify!(NewAxis))?,
+            AxisSliceInfo::NewAxis => write!(f, stringify!(NewAxis))?,
         }
         Ok(())
     }
@@ -223,9 +247,9 @@ macro_rules! impl_slice_variant_from_range {
 impl_slice_variant_from_range!(Slice, Slice, isize);
 impl_slice_variant_from_range!(Slice, Slice, usize);
 impl_slice_variant_from_range!(Slice, Slice, i32);
-impl_slice_variant_from_range!(SliceInfoElem, SliceInfoElem::Slice, isize);
-impl_slice_variant_from_range!(SliceInfoElem, SliceInfoElem::Slice, usize);
-impl_slice_variant_from_range!(SliceInfoElem, SliceInfoElem::Slice, i32);
+impl_slice_variant_from_range!(AxisSliceInfo, AxisSliceInfo::Slice, isize);
+impl_slice_variant_from_range!(AxisSliceInfo, AxisSliceInfo::Slice, usize);
+impl_slice_variant_from_range!(AxisSliceInfo, AxisSliceInfo::Slice, i32);
 
 impl From<RangeFull> for Slice {
     #[inline]
@@ -238,10 +262,10 @@ impl From<RangeFull> for Slice {
     }
 }
 
-impl From<RangeFull> for SliceInfoElem {
+impl From<RangeFull> for AxisSliceInfo {
     #[inline]
-    fn from(_: RangeFull) -> SliceInfoElem {
-        SliceInfoElem::Slice {
+    fn from(_: RangeFull) -> AxisSliceInfo {
+        AxisSliceInfo::Slice {
             start: 0,
             end: None,
             step: 1,
@@ -249,10 +273,10 @@ impl From<RangeFull> for SliceInfoElem {
     }
 }
 
-impl From<Slice> for SliceInfoElem {
+impl From<Slice> for AxisSliceInfo {
     #[inline]
-    fn from(s: Slice) -> SliceInfoElem {
-        SliceInfoElem::Slice {
+    fn from(s: Slice) -> AxisSliceInfo {
+        AxisSliceInfo::Slice {
             start: s.start,
             end: s.end,
             step: s.step,
@@ -260,24 +284,24 @@ impl From<Slice> for SliceInfoElem {
     }
 }
 
-macro_rules! impl_sliceinfoelem_from_index {
+macro_rules! impl_axissliceinfo_from_index {
     ($index:ty) => {
-        impl From<$index> for SliceInfoElem {
+        impl From<$index> for AxisSliceInfo {
             #[inline]
-            fn from(r: $index) -> SliceInfoElem {
-                SliceInfoElem::Index(r as isize)
+            fn from(r: $index) -> AxisSliceInfo {
+                AxisSliceInfo::Index(r as isize)
             }
         }
     };
 }
-impl_sliceinfoelem_from_index!(isize);
-impl_sliceinfoelem_from_index!(usize);
-impl_sliceinfoelem_from_index!(i32);
+impl_axissliceinfo_from_index!(isize);
+impl_axissliceinfo_from_index!(usize);
+impl_axissliceinfo_from_index!(i32);
 
-impl From<NewAxis> for SliceInfoElem {
+impl From<NewAxis> for AxisSliceInfo {
     #[inline]
-    fn from(_: NewAxis) -> SliceInfoElem {
-        SliceInfoElem::NewAxis
+    fn from(_: NewAxis) -> AxisSliceInfo {
+        AxisSliceInfo::NewAxis
     }
 }
 
@@ -285,9 +309,9 @@ impl From<NewAxis> for SliceInfoElem {
 ///
 /// This trait is unsafe to implement because the implementation must ensure
 /// that `D`, `Self::OutDim`, `self.in_dim()`, and `self.out_ndim()` are
-/// consistent with the `&[SliceInfoElem]` returned by `self.as_ref()` and that
+/// consistent with the `&[AxisSliceInfo]` returned by `self.as_ref()` and that
 /// `self.as_ref()` always returns the same value when called multiple times.
-pub unsafe trait SliceArg<D: Dimension>: AsRef<[SliceInfoElem]> {
+pub unsafe trait SliceArg<D: Dimension>: AsRef<[AxisSliceInfo]> {
     /// Dimensionality of the output array.
     type OutDim: Dimension;
 
@@ -300,29 +324,11 @@ pub unsafe trait SliceArg<D: Dimension>: AsRef<[SliceInfoElem]> {
     private_decl! {}
 }
 
-unsafe impl<T, D> SliceArg<D> for &T
-where
-    T: SliceArg<D> + ?Sized,
-    D: Dimension,
-{
-    type OutDim = T::OutDim;
-
-    fn in_ndim(&self) -> usize {
-        T::in_ndim(self)
-    }
-
-    fn out_ndim(&self) -> usize {
-        T::out_ndim(self)
-    }
-
-    private_impl! {}
-}
-
 macro_rules! impl_slicearg_samedim {
     ($in_dim:ty) => {
         unsafe impl<T, Dout> SliceArg<$in_dim> for SliceInfo<T, $in_dim, Dout>
         where
-            T: AsRef<[SliceInfoElem]>,
+            T: AsRef<[AxisSliceInfo]>,
             Dout: Dimension,
         {
             type OutDim = Dout;
@@ -349,7 +355,7 @@ impl_slicearg_samedim!(Ix6);
 
 unsafe impl<T, Din, Dout> SliceArg<IxDyn> for SliceInfo<T, Din, Dout>
 where
-    T: AsRef<[SliceInfoElem]>,
+    T: AsRef<[AxisSliceInfo]>,
     Din: Dimension,
     Dout: Dimension,
 {
@@ -366,7 +372,7 @@ where
     private_impl! {}
 }
 
-unsafe impl SliceArg<IxDyn> for [SliceInfoElem] {
+unsafe impl SliceArg<IxDyn> for [AxisSliceInfo] {
     type OutDim = IxDyn;
 
     fn in_ndim(&self) -> usize {
@@ -382,8 +388,8 @@ unsafe impl SliceArg<IxDyn> for [SliceInfoElem] {
 
 /// Represents all of the necessary information to perform a slice.
 ///
-/// The type `T` is typically `[SliceInfoElem; n]`, `&[SliceInfoElem]`, or
-/// `Vec<SliceInfoElem>`. The type `Din` is the dimension of the array to be
+/// The type `T` is typically `[AxisSliceInfo; n]`, `[AxisSliceInfo]`, or
+/// `Vec<AxisSliceInfo>`. The type `Din` is the dimension of the array to be
 /// sliced, and `Dout` is the output dimension after calling [`.slice()`]. Note
 /// that if `Din` is a fixed dimension type (`Ix0`, `Ix1`, `Ix2`, etc.), the
 /// `SliceInfo` instance can still be used to slice an array with dimension
@@ -391,13 +397,14 @@ unsafe impl SliceArg<IxDyn> for [SliceInfoElem] {
 ///
 /// [`.slice()`]: struct.ArrayBase.html#method.slice
 #[derive(Debug)]
-pub struct SliceInfo<T, Din: Dimension, Dout: Dimension> {
+#[repr(transparent)]
+pub struct SliceInfo<T: ?Sized, Din: Dimension, Dout: Dimension> {
     in_dim: PhantomData<Din>,
     out_dim: PhantomData<Dout>,
     indices: T,
 }
 
-impl<T, Din, Dout> Deref for SliceInfo<T, Din, Dout>
+impl<T: ?Sized, Din, Dout> Deref for SliceInfo<T, Din, Dout>
 where
     Din: Dimension,
     Dout: Dimension,
@@ -408,7 +415,7 @@ where
     }
 }
 
-fn check_dims_for_sliceinfo<Din, Dout>(indices: &[SliceInfoElem]) -> Result<(), ShapeError>
+fn check_dims_for_sliceinfo<Din, Dout>(indices: &[AxisSliceInfo]) -> Result<(), ShapeError>
 where
     Din: Dimension,
     Dout: Dimension,
@@ -428,7 +435,7 @@ where
 
 impl<T, Din, Dout> SliceInfo<T, Din, Dout>
 where
-    T: AsRef<[SliceInfoElem]>,
+    T: AsRef<[AxisSliceInfo]>,
     Din: Dimension,
     Dout: Dimension,
 {
@@ -457,7 +464,14 @@ where
             indices,
         }
     }
+}
 
+impl<T, Din, Dout> SliceInfo<T, Din, Dout>
+where
+    T: AsRef<[AxisSliceInfo]>,
+    Din: Dimension,
+    Dout: Dimension,
+{
     /// Returns a new `SliceInfo` instance.
     ///
     /// Errors if `Din` or `Dout` is not consistent with `indices`.
@@ -476,13 +490,20 @@ where
             indices,
         })
     }
+}
 
+impl<T: ?Sized, Din, Dout> SliceInfo<T, Din, Dout>
+where
+    T: AsRef<[AxisSliceInfo]>,
+    Din: Dimension,
+    Dout: Dimension,
+{
     /// Returns the number of dimensions of the input array for
     /// [`.slice()`](struct.ArrayBase.html#method.slice).
     ///
     /// If `Din` is a fixed-size dimension type, then this is equivalent to
     /// `Din::NDIM.unwrap()`. Otherwise, the value is calculated by iterating
-    /// over the `SliceInfoElem` elements.
+    /// over the `AxisSliceInfo` elements.
     pub fn in_ndim(&self) -> usize {
         if let Some(ndim) = Din::NDIM {
             ndim
@@ -497,7 +518,7 @@ where
     ///
     /// If `Dout` is a fixed-size dimension type, then this is equivalent to
     /// `Dout::NDIM.unwrap()`. Otherwise, the value is calculated by iterating
-    /// over the `SliceInfoElem` elements.
+    /// over the `AxisSliceInfo` elements.
     pub fn out_ndim(&self) -> usize {
         if let Some(ndim) = Dout::NDIM {
             ndim
@@ -507,7 +528,7 @@ where
     }
 }
 
-impl<'a, Din, Dout> TryFrom<&'a [SliceInfoElem]> for SliceInfo<&'a [SliceInfoElem], Din, Dout>
+impl<'a, Din, Dout> TryFrom<&'a [AxisSliceInfo]> for &'a SliceInfo<[AxisSliceInfo], Din, Dout>
 where
     Din: Dimension,
     Dout: Dimension,
@@ -515,17 +536,22 @@ where
     type Error = ShapeError;
 
     fn try_from(
-        indices: &'a [SliceInfoElem],
-    ) -> Result<SliceInfo<&'a [SliceInfoElem], Din, Dout>, ShapeError> {
+        indices: &'a [AxisSliceInfo],
+    ) -> Result<&'a SliceInfo<[AxisSliceInfo], Din, Dout>, ShapeError> {
+        check_dims_for_sliceinfo::<Din, Dout>(indices)?;
         unsafe {
-            // This is okay because `&[SliceInfoElem]` always returns the same
-            // value for `.as_ref()`.
-            Self::new(indices)
+            // This is okay because we've already checked the correctness of
+            // `Din` and `Dout`, and the only non-zero-sized member of
+            // `SliceInfo` is `indices`, so `&SliceInfo<[AxisSliceInfo], Din,
+            // Dout>` should have the same bitwise representation as
+            // `&[AxisSliceInfo]`.
+            Ok(&*(indices as *const [AxisSliceInfo]
+                as *const SliceInfo<[AxisSliceInfo], Din, Dout>))
         }
     }
 }
 
-impl<Din, Dout> TryFrom<Vec<SliceInfoElem>> for SliceInfo<Vec<SliceInfoElem>, Din, Dout>
+impl<Din, Dout> TryFrom<Vec<AxisSliceInfo>> for SliceInfo<Vec<AxisSliceInfo>, Din, Dout>
 where
     Din: Dimension,
     Dout: Dimension,
@@ -533,8 +559,8 @@ where
     type Error = ShapeError;
 
     fn try_from(
-        indices: Vec<SliceInfoElem>,
-    ) -> Result<SliceInfo<Vec<SliceInfoElem>, Din, Dout>, ShapeError> {
+        indices: Vec<AxisSliceInfo>,
+    ) -> Result<SliceInfo<Vec<AxisSliceInfo>, Din, Dout>, ShapeError> {
         unsafe {
             // This is okay because `Vec` always returns the same value for
             // `.as_ref()`.
@@ -545,8 +571,8 @@ where
 
 macro_rules! impl_tryfrom_array_for_sliceinfo {
     ($len:expr) => {
-        impl<Din, Dout> TryFrom<[SliceInfoElem; $len]>
-            for SliceInfo<[SliceInfoElem; $len], Din, Dout>
+        impl<Din, Dout> TryFrom<[AxisSliceInfo; $len]>
+            for SliceInfo<[AxisSliceInfo; $len], Din, Dout>
         where
             Din: Dimension,
             Dout: Dimension,
@@ -554,10 +580,10 @@ macro_rules! impl_tryfrom_array_for_sliceinfo {
             type Error = ShapeError;
 
             fn try_from(
-                indices: [SliceInfoElem; $len],
-            ) -> Result<SliceInfo<[SliceInfoElem; $len], Din, Dout>, ShapeError> {
+                indices: [AxisSliceInfo; $len],
+            ) -> Result<SliceInfo<[AxisSliceInfo; $len], Din, Dout>, ShapeError> {
                 unsafe {
-                    // This is okay because `[SliceInfoElem; N]` always returns
+                    // This is okay because `[AxisSliceInfo; N]` always returns
                     // the same value for `.as_ref()`.
                     Self::new(indices)
                 }
@@ -575,29 +601,31 @@ impl_tryfrom_array_for_sliceinfo!(6);
 impl_tryfrom_array_for_sliceinfo!(7);
 impl_tryfrom_array_for_sliceinfo!(8);
 
-impl<T, Din, Dout> AsRef<[SliceInfoElem]> for SliceInfo<T, Din, Dout>
+impl<T, Din, Dout> AsRef<[AxisSliceInfo]> for SliceInfo<T, Din, Dout>
 where
-    T: AsRef<[SliceInfoElem]>,
+    T: AsRef<[AxisSliceInfo]>,
     Din: Dimension,
     Dout: Dimension,
 {
-    fn as_ref(&self) -> &[SliceInfoElem] {
+    fn as_ref(&self) -> &[AxisSliceInfo] {
         self.indices.as_ref()
     }
 }
 
-impl<'a, T, Din, Dout> From<&'a SliceInfo<T, Din, Dout>>
-    for SliceInfo<&'a [SliceInfoElem], Din, Dout>
+impl<T, Din, Dout> AsRef<SliceInfo<[AxisSliceInfo], Din, Dout>> for SliceInfo<T, Din, Dout>
 where
-    T: AsRef<[SliceInfoElem]>,
+    T: AsRef<[AxisSliceInfo]>,
     Din: Dimension,
     Dout: Dimension,
 {
-    fn from(info: &'a SliceInfo<T, Din, Dout>) -> SliceInfo<&'a [SliceInfoElem], Din, Dout> {
-        SliceInfo {
-            in_dim: info.in_dim,
-            out_dim: info.out_dim,
-            indices: info.indices.as_ref(),
+    fn as_ref(&self) -> &SliceInfo<[AxisSliceInfo], Din, Dout> {
+        unsafe {
+            // This is okay because the only non-zero-sized member of
+            // `SliceInfo` is `indices`, so `&SliceInfo<[AxisSliceInfo], Din, Dout>`
+            // should have the same bitwise representation as
+            // `&[AxisSliceInfo]`.
+            &*(self.indices.as_ref() as *const [AxisSliceInfo]
+                as *const SliceInfo<[AxisSliceInfo], Din, Dout>)
         }
     }
 }
@@ -675,14 +703,16 @@ impl_slicenextdim!((), NewAxis, Ix0, Ix1);
 ///
 /// `s![]` takes a list of ranges/slices/indices/new-axes, separated by comma,
 /// with optional step sizes that are separated from the range by a semicolon.
-/// It is converted into a [`SliceInfo`] instance.
+/// It is converted into a [`&SliceInfo`] instance.
+///
+/// [`&SliceInfo`]: struct.SliceInfo.html
 ///
 /// Each range/slice/index uses signed indices, where a negative value is
 /// counted from the end of the axis. Step sizes are also signed and may be
 /// negative, but must not be zero.
 ///
-/// The syntax is `s![` *[ elem [, elem [ , ... ] ] ]* `]`, where *elem* is any
-/// of the following:
+/// The syntax is `s![` *[ axis-slice-info [, axis-slice-info [ , ... ] ] ]*
+/// `]`, where *axis-slice-info* is any of the following:
 ///
 /// * *index*: an index to use for taking a subview with respect to that axis.
 ///   (The index is selected. The axis is removed except with
@@ -690,15 +720,15 @@ impl_slicenextdim!((), NewAxis, Ix0, Ix1);
 /// * *range*: a range with step size 1 to use for slicing that axis.
 /// * *range* `;` *step*: a range with step size *step* to use for slicing that axis.
 /// * *slice*: a [`Slice`] instance to use for slicing that axis.
-/// * *slice* `;` *step*: a range constructed from a [`Slice`] instance,
-///   multiplying the step size by *step*, to use for slicing that axis.
+/// * *slice* `;` *step*: a range constructed from the start and end of a [`Slice`]
+///   instance, with new step size *step*, to use for slicing that axis.
 /// * *new-axis*: a [`NewAxis`] instance that represents the creation of a new axis.
 ///   (Except for [`.slice_collapse()`], which panics on [`NewAxis`] elements.)
 ///
 /// [`Slice`]: struct.Slice.html
 /// [`NewAxis`]: struct.NewAxis.html
 ///
-/// The number of *elem*, not including *new-axis*, must match the
+/// The number of *axis-slice-info*, not including *new-axis*, must match the
 /// number of axes in the array. *index*, *range*, *slice*, *step*, and
 /// *new-axis* can be expressions. *index* must be of type `isize`, `usize`, or
 /// `i32`. *range* must be of type `Range<I>`, `RangeTo<I>`, `RangeFrom<I>`, or
@@ -850,18 +880,18 @@ macro_rules! s(
     };
     // Catch-all clause for syntax errors
     (@parse $($t:tt)*) => { compile_error!("Invalid syntax in s![] call.") };
-    // convert range/index/new-axis into SliceInfoElem
+    // convert range/index/new-axis into AxisSliceInfo
     (@convert $r:expr) => {
-        <$crate::SliceInfoElem as ::std::convert::From<_>>::from($r)
+        <$crate::AxisSliceInfo as ::std::convert::From<_>>::from($r)
     };
-    // convert range/index/new-axis and step into SliceInfoElem
+    // convert range/index/new-axis and step into AxisSliceInfo
     (@convert $r:expr, $s:expr) => {
-        <$crate::SliceInfoElem as ::std::convert::From<_>>::from(
-            <$crate::Slice as ::std::convert::From<_>>::from($r).step_by($s as isize)
-        )
+        <$crate::AxisSliceInfo as ::std::convert::From<_>>::from($r).step_by($s as isize)
     };
     ($($t:tt)*) => {
-        $crate::s![@parse
+        // The extra `*&` is a workaround for this compiler bug:
+        // https://github.com/rust-lang/rust/issues/23014
+        &*&$crate::s![@parse
               ::std::marker::PhantomData::<$crate::Ix0>,
               ::std::marker::PhantomData::<$crate::Ix0>,
               []
@@ -903,7 +933,7 @@ where
     private_impl! {}
 }
 
-impl<'a, A, D, I0> MultiSliceArg<'a, A, D> for (I0,)
+impl<'a, A, D, I0> MultiSliceArg<'a, A, D> for (&I0,)
 where
     A: 'a,
     D: Dimension,
@@ -912,7 +942,7 @@ where
     type Output = (ArrayViewMut<'a, A, I0::OutDim>,);
 
     fn multi_slice_move(&self, view: ArrayViewMut<'a, A, D>) -> Self::Output {
-        (view.slice_move(&self.0),)
+        (view.slice_move(self.0),)
     }
 
     private_impl! {}
@@ -923,7 +953,7 @@ macro_rules! impl_multislice_tuple {
         impl_multislice_tuple!(@def_impl ($($but_last,)* $last,), [$($but_last)*] $last);
     };
     (@def_impl ($($all:ident,)*), [$($but_last:ident)*] $last:ident) => {
-        impl<'a, A, D, $($all,)*> MultiSliceArg<'a, A, D> for ($($all,)*)
+        impl<'a, A, D, $($all,)*> MultiSliceArg<'a, A, D> for ($(&$all,)*)
         where
             A: 'a,
             D: Dimension,
@@ -933,7 +963,7 @@ macro_rules! impl_multislice_tuple {
 
             fn multi_slice_move(&self, view: ArrayViewMut<'a, A, D>) -> Self::Output {
                 #[allow(non_snake_case)]
-                let ($($all,)*) = self;
+                let &($($all,)*) = self;
 
                 let shape = view.raw_dim();
                 assert!(!impl_multislice_tuple!(@intersects_self &shape, ($($all,)*)));
diff --git a/src/stacking.rs b/src/stacking.rs
index 500ded6a..580d94b4 100644
--- a/src/stacking.rs
+++ b/src/stacking.rs
@@ -8,6 +8,7 @@
 
 use crate::error::{from_kind, ErrorKind, ShapeError};
 use crate::imp_prelude::*;
+use crate::traversal_utils::assign_to;
 
 /// Stack arrays along the new axis.
 ///
@@ -98,7 +99,7 @@ where
         for array in arrays {
             let len = array.len_of(axis);
             let (front, rest) = assign_view.split_at(axis, len);
-            array.assign_to(front);
+            assign_to(array, front);
             assign_view = rest;
         }
         debug_assert_eq!(assign_view.len(), 0);
@@ -170,7 +171,7 @@ where
             // but same number of axes).
             let assign_view = assign_view.into_dimensionality::<D>()
                 .expect("same-dimensionality cast");
-            array.assign_to(assign_view);
+            assign_to(array, assign_view);
         });
 
     unsafe {
diff --git a/src/traversal_utils.rs b/src/traversal_utils.rs
new file mode 100644
index 00000000..3f4d017b
--- /dev/null
+++ b/src/traversal_utils.rs
@@ -0,0 +1,26 @@
+// Copyright 2020 bluss and ndarray developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::{
+    IntoNdProducer,
+    AssignElem,
+    Zip,
+};
+
+/// Assign values from producer P1 to producer P2
+/// P1 and P2 must be of the same shape and dimension
+pub(crate) fn assign_to<'a, P1, P2, A>(from: P1, to: P2)
+    where P1: IntoNdProducer<Item = &'a A>,
+          P2: IntoNdProducer<Dim = P1::Dim>,
+          P2::Item: AssignElem<A>,
+          A: Clone + 'a
+{
+    Zip::from(from)
+        .map_assign_into(to, A::clone);
+}
+


Test patch:
diff --git a/blas-tests/tests/oper.rs b/blas-tests/tests/oper.rs
index d6230cce..6b6797f1 100644
--- a/blas-tests/tests/oper.rs
+++ b/blas-tests/tests/oper.rs
@@ -6,7 +6,8 @@ extern crate num_traits;
 use ndarray::linalg::general_mat_mul;
 use ndarray::linalg::general_mat_vec_mul;
 use ndarray::prelude::*;
-use ndarray::{Data, Ix, Ixs, LinalgScalar, Slice, SliceInfoElem};
+use ndarray::{AxisSliceInfo, Ix, Ixs};
+use ndarray::{Data, LinalgScalar};
 
 use approx::{assert_abs_diff_eq, assert_relative_eq};
 use defmac::defmac;
@@ -418,12 +419,12 @@ fn scaled_add_3() {
                 let mut a = range_mat64(m, k);
                 let mut answer = a.clone();
                 let cdim = if n == 1 { vec![q] } else { vec![n, q] };
-                let cslice: Vec<SliceInfoElem> = if n == 1 {
-                    vec![Slice::from(..).step_by(s2).into()]
+                let cslice = if n == 1 {
+                    vec![AxisSliceInfo::from(..).step_by(s2)]
                 } else {
                     vec![
-                        Slice::from(..).step_by(s1).into(),
-                        Slice::from(..).step_by(s2).into(),
+                        AxisSliceInfo::from(..).step_by(s1),
+                        AxisSliceInfo::from(..).step_by(s2),
                     ]
                 };
 
diff --git a/tests/array.rs b/tests/array.rs
index b1d37da7..fa5da441 100644
--- a/tests/array.rs
+++ b/tests/array.rs
@@ -12,7 +12,7 @@ use itertools::{enumerate, zip, Itertools};
 use ndarray::prelude::*;
 use ndarray::{arr3, rcarr2};
 use ndarray::indices;
-use ndarray::{Slice, SliceInfo, SliceInfoElem};
+use ndarray::{AxisSliceInfo, Slice, SliceInfo};
 use std::convert::TryFrom;
 
 macro_rules! assert_panics {
@@ -219,20 +219,20 @@ fn test_slice_dyninput_array_fixed() {
 #[test]
 fn test_slice_array_dyn() {
     let mut arr = Array3::<f64>::zeros((5, 2, 5));
-    let info = SliceInfo::<_, Ix3, IxDyn>::try_from([
-        SliceInfoElem::from(1..),
-        SliceInfoElem::from(1),
-        SliceInfoElem::from(NewAxis),
-        SliceInfoElem::from(Slice::from(..).step_by(2)),
+    let info = &SliceInfo::<_, Ix3, IxDyn>::try_from([
+        AxisSliceInfo::from(1..),
+        AxisSliceInfo::from(1),
+        AxisSliceInfo::from(NewAxis),
+        AxisSliceInfo::from(..).step_by(2),
     ])
     .unwrap();
     arr.slice(info);
     arr.slice_mut(info);
     arr.view().slice_move(info);
-    let info2 = SliceInfo::<_, Ix3, IxDyn>::try_from([
-        SliceInfoElem::from(1..),
-        SliceInfoElem::from(1),
-        SliceInfoElem::from(Slice::from(..).step_by(2)),
+    let info2 = &SliceInfo::<_, Ix3, IxDyn>::try_from([
+        AxisSliceInfo::from(1..),
+        AxisSliceInfo::from(1),
+        AxisSliceInfo::from(..).step_by(2),
     ])
     .unwrap();
     arr.view().slice_collapse(info2);
@@ -241,20 +241,20 @@ fn test_slice_array_dyn() {
 #[test]
 fn test_slice_dyninput_array_dyn() {
     let mut arr = Array3::<f64>::zeros((5, 2, 5)).into_dyn();
-    let info = SliceInfo::<_, Ix3, IxDyn>::try_from([
-        SliceInfoElem::from(1..),
-        SliceInfoElem::from(1),
-        SliceInfoElem::from(NewAxis),
-        SliceInfoElem::from(Slice::from(..).step_by(2)),
+    let info = &SliceInfo::<_, Ix3, IxDyn>::try_from([
+        AxisSliceInfo::from(1..),
+        AxisSliceInfo::from(1),
+        AxisSliceInfo::from(NewAxis),
+        AxisSliceInfo::from(..).step_by(2),
     ])
     .unwrap();
     arr.slice(info);
     arr.slice_mut(info);
     arr.view().slice_move(info);
-    let info2 = SliceInfo::<_, Ix3, IxDyn>::try_from([
-        SliceInfoElem::from(1..),
-        SliceInfoElem::from(1),
-        SliceInfoElem::from(Slice::from(..).step_by(2)),
+    let info2 = &SliceInfo::<_, Ix3, IxDyn>::try_from([
+        AxisSliceInfo::from(1..),
+        AxisSliceInfo::from(1),
+        AxisSliceInfo::from(..).step_by(2),
     ])
     .unwrap();
     arr.view().slice_collapse(info2);
@@ -264,19 +264,19 @@ fn test_slice_dyninput_array_dyn() {
 fn test_slice_dyninput_vec_fixed() {
     let mut arr = Array3::<f64>::zeros((5, 2, 5)).into_dyn();
     let info = &SliceInfo::<_, Ix3, Ix3>::try_from(vec![
-        SliceInfoElem::from(1..),
-        SliceInfoElem::from(1),
-        SliceInfoElem::from(NewAxis),
-        SliceInfoElem::from(Slice::from(..).step_by(2)),
+        AxisSliceInfo::from(1..),
+        AxisSliceInfo::from(1),
+        AxisSliceInfo::from(NewAxis),
+        AxisSliceInfo::from(..).step_by(2),
     ])
     .unwrap();
     arr.slice(info);
     arr.slice_mut(info);
     arr.view().slice_move(info);
-    let info2 = SliceInfo::<_, Ix3, Ix2>::try_from(vec![
-        SliceInfoElem::from(1..),
-        SliceInfoElem::from(1),
-        SliceInfoElem::from(Slice::from(..).step_by(2)),
+    let info2 = &SliceInfo::<_, Ix3, Ix2>::try_from(vec![
+        AxisSliceInfo::from(1..),
+        AxisSliceInfo::from(1),
+        AxisSliceInfo::from(..).step_by(2),
     ])
     .unwrap();
     arr.view().slice_collapse(info2);
@@ -286,19 +286,19 @@ fn test_slice_dyninput_vec_fixed() {
 fn test_slice_dyninput_vec_dyn() {
     let mut arr = Array3::<f64>::zeros((5, 2, 5)).into_dyn();
     let info = &SliceInfo::<_, Ix3, IxDyn>::try_from(vec![
-        SliceInfoElem::from(1..),
-        SliceInfoElem::from(1),
-        SliceInfoElem::from(NewAxis),
-        SliceInfoElem::from(Slice::from(..).step_by(2)),
+        AxisSliceInfo::from(1..),
+        AxisSliceInfo::from(1),
+        AxisSliceInfo::from(NewAxis),
+        AxisSliceInfo::from(..).step_by(2),
     ])
     .unwrap();
     arr.slice(info);
     arr.slice_mut(info);
     arr.view().slice_move(info);
-    let info2 = SliceInfo::<_, Ix3, IxDyn>::try_from(vec![
-        SliceInfoElem::from(1..),
-        SliceInfoElem::from(1),
-        SliceInfoElem::from(Slice::from(..).step_by(2)),
+    let info2 = &SliceInfo::<_, Ix3, IxDyn>::try_from(vec![
+        AxisSliceInfo::from(1..),
+        AxisSliceInfo::from(1),
+        AxisSliceInfo::from(..).step_by(2),
     ])
     .unwrap();
     arr.view().slice_collapse(info2);
@@ -900,6 +900,31 @@ fn standard_layout() {
     assert!(x4.is_standard_layout());
 }
 
+#[test]
+fn assign() {
+    let mut a = arr2(&[[1., 2.], [3., 4.]]);
+    let b = arr2(&[[1., 3.], [2., 4.]]);
+    a.assign(&b);
+    assert_eq!(a, b);
+
+    /* Test broadcasting */
+    a.assign(&ArcArray::zeros(1));
+    assert_eq!(a, ArcArray::zeros((2, 2)));
+
+    /* Test other type */
+    a.assign(&Array::from_elem((2, 2), 3.));
+    assert_eq!(a, ArcArray::from_elem((2, 2), 3.));
+
+    /* Test mut view */
+    let mut a = arr2(&[[1, 2], [3, 4]]);
+    {
+        let mut v = a.view_mut();
+        v.slice_collapse(s![..1, ..]);
+        v.fill(0);
+    }
+    assert_eq!(a, arr2(&[[0, 0], [3, 4]]));
+}
+
 #[test]
 fn iter_size_hint() {
     let mut a = arr2(&[[1., 2.], [3., 4.]]);
diff --git a/tests/higher_order_f.rs b/tests/higher_order_f.rs
index 1238cc4d..c567eb3e 100644
--- a/tests/higher_order_f.rs
+++ b/tests/higher_order_f.rs
@@ -6,37 +6,3 @@ fn test_fold_axis_oob() {
     let a = arr2(&[[1., 2.], [3., 4.]]);
     a.fold_axis(Axis(2), 0., |x, y| x + y);
 }
-
-#[test]
-fn assign() {
-    let mut a = arr2(&[[1., 2.], [3., 4.]]);
-    let b = arr2(&[[1., 3.], [2., 4.]]);
-    a.assign(&b);
-    assert_eq!(a, b);
-
-    /* Test broadcasting */
-    a.assign(&ArcArray::zeros(1));
-    assert_eq!(a, ArcArray::zeros((2, 2)));
-
-    /* Test other type */
-    a.assign(&Array::from_elem((2, 2), 3.));
-    assert_eq!(a, ArcArray::from_elem((2, 2), 3.));
-
-    /* Test mut view */
-    let mut a = arr2(&[[1, 2], [3, 4]]);
-    {
-        let mut v = a.view_mut();
-        v.slice_collapse(s![..1, ..]);
-        v.fill(0);
-    }
-    assert_eq!(a, arr2(&[[0, 0], [3, 4]]));
-}
-
-
-#[test]
-fn assign_to() {
-    let mut a = arr2(&[[1., 2.], [3., 4.]]);
-    let b = arr2(&[[0., 3.], [2., 0.]]);
-    b.assign_to(&mut a);
-    assert_eq!(a, b);
-}
diff --git a/tests/oper.rs b/tests/oper.rs
index ed612bad..b91a9bd8 100644
--- a/tests/oper.rs
+++ b/tests/oper.rs
@@ -561,7 +561,7 @@ fn scaled_add_2() {
 #[test]
 fn scaled_add_3() {
     use approx::assert_relative_eq;
-    use ndarray::{Slice, SliceInfo, SliceInfoElem};
+    use ndarray::{SliceInfo, AxisSliceInfo};
     use std::convert::TryFrom;
 
     let beta = -2.3;
@@ -583,12 +583,12 @@ fn scaled_add_3() {
                 let mut a = range_mat64(m, k);
                 let mut answer = a.clone();
                 let cdim = if n == 1 { vec![q] } else { vec![n, q] };
-                let cslice: Vec<SliceInfoElem> = if n == 1 {
-                    vec![Slice::from(..).step_by(s2).into()]
+                let cslice = if n == 1 {
+                    vec![AxisSliceInfo::from(..).step_by(s2)]
                 } else {
                     vec![
-                        Slice::from(..).step_by(s1).into(),
-                        Slice::from(..).step_by(s2).into(),
+                        AxisSliceInfo::from(..).step_by(s1),
+                        AxisSliceInfo::from(..).step_by(s2),
                     ]
                 };
 
@@ -596,7 +596,7 @@ fn scaled_add_3() {
 
                 {
                     let mut av = a.slice_mut(s![..;s1, ..;s2]);
-                    let c = c.slice(SliceInfo::<_, IxDyn, IxDyn>::try_from(cslice).unwrap());
+                    let c = c.slice(&SliceInfo::<_, IxDyn, IxDyn>::try_from(cslice).unwrap());
 
                     let mut answerv = answer.slice_mut(s![..;s1, ..;s2]);
                     answerv += &(beta * &c);