Repository: rust-ndarray/ndarray
Pull request #844 (2020-09-25 09:33:14)

Stack and concatenate
Issue: https://github.com/rust-ndarray/ndarray/issues/667

Continuation of https://github.com/rust-ndarray/ndarray/pull/735 with the commit history being cleaned up.

Changes:
 - added `concatenate` proxy function to `stack` function, `concatenate!` proxy macro to `stack!` macro
 - deprecated `stack` function and `stack!` macro since `0.13.2`
 - added `stack_new_axis` function and `stack_new_axis!` macro to mimic the behavior of `np.stack` function from numpy

In 0.14.0 we should probably rename `stack_new_axis` to `stack` and drop old `stack` function, same about macro.

##### concatenate
```rust
use ndarray::{arr2, Axis, concatenate};

let a = arr2(&[[2., 2.], [3., 3.]]);
assert!(
     concatenate(Axis(0), &[a.view(), a.view()])
     == Ok(arr2(&[[2., 2.], [3., 3.], [2., 2.], [3., 3.]]))
);
```

##### stack_new_axis
```rust
use ndarray::{arr2, arr3, stack_new_axis, Axis};

let a = arr2(&[[2., 2.],
               [3., 3.]]);
assert!(
    stack_new_axis![Axis(0), a, a]
    == arr3(&[[[2., 2.],
               [3., 3.]],
              [[2., 2.],
               [3., 3.]]])
);
```


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 86eabb48..3da63d1d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 
 name = "ndarray"
-version = "0.13.1"
+version = "0.13.0"
 edition = "2018"
 authors = [
   "bluss",
diff --git a/README-quick-start.md b/README-quick-start.md
deleted file mode 100644
index 2adc04d7..00000000
--- a/README-quick-start.md
+++ /dev/null
@@ -1,606 +0,0 @@
-# Quickstart tutorial
-
-If you are familiar with Python Numpy, do check out this [For Numpy User Doc](https://docs.rs/ndarray/0.13.0/ndarray/doc/ndarray_for_numpy_users/index.html)
-after you go through this tutorial. 
-
-You can use [play.integer32.com](https://play.integer32.com/) to immediately try out the examples.
-
-## The Basics
-
-Just create your first 2x3 floating-point ndarray 
-```rust
-use ndarray::prelude::*;
-
-fn main() {
-    let a = array![
-                [1.,2.,3.], 
-                [4.,5.,6.],
-            ]; 
-    assert_eq!(a.ndim(), 2);         // get the number of dimensions of array a
-    assert_eq!(a.len(), 6);          // get the number of elements in array a
-    assert_eq!(a.shape(), [2, 3]);   // get the shape of array a
-    assert_eq!(a.is_empty(), false); // check if the array has zero elements
-
-    println!("{:?}", a);
-}
-```
-This code will create a simple array and output to stdout:
-```
-[[1.0, 2.0, 3.0],
- [4.0, 5.0, 6.0]], shape=[2, 3], strides=[3, 1], layout=C (0x1), const ndim=2
-```
-
-## Array Creation
-
-### Element type and dimensionality
-
-Now let's create more arrays. How about try make a zero array with dimension of (3, 2, 4)?
-
-```rust
-use ndarray::prelude::*;
-use ndarray::Array;
-fn main() {
-    let a = Array::zeros((3, 2, 4).f());
-    println!("{:?}", a);
-}
-```
-gives
-```
-|    let a = Array::zeros((3, 2, 4).f());
-|        -   ^^^^^^^^^^^^ cannot infer type for type parameter `A`
-```
-Note that the compiler needs to infer the element type and dimensionality from context. In this 
-case the compiler failed to do that. Now we give it the type and let it infer dimensionality
-
-```rust
-use ndarray::prelude::*;
-use ndarray::Array;
-fn main() {
-  let a = Array::<f64, _>::zeros((3, 2, 4).f());
-  println!("{:?}", a);
-}
-```
-and now it works:
-```
-[[[0.0, 0.0, 0.0, 0.0],
-  [0.0, 0.0, 0.0, 0.0]],
-
- [[0.0, 0.0, 0.0, 0.0],
-  [0.0, 0.0, 0.0, 0.0]],
-
- [[0.0, 0.0, 0.0, 0.0],
-  [0.0, 0.0, 0.0, 0.0]]], shape=[3, 2, 4], strides=[1, 3, 6], layout=F (0x2), const ndim=3
-```
-
-We can also specify its dimensionality
-
-```rust
-use ndarray::prelude::*;
-use ndarray::{Array, Ix3};
-fn main() {
-  let a = Array::<f64, Ix3>::zeros((3, 2, 4).f());
-  println!("{:?}", a);
-}
-```
-`Ix3` stands for 3D array.
-
-And now we are type checked. Try change the code above to `Array::<f64, Ix3>::zeros((3, 2, 4, 5).f());`
-and compile, see what happens.
-
-### How about create array of different type and having different initial values?
-
-The [`from_elm`](http://docs.rs/ndarray/latest/ndarray/struct.ArrayBase.html#method.from_elem) method can be handy here:
-
-```rust
-use ndarray::{Array, Ix3};
-fn main() {
-  let a = Array::<bool, Ix3>::from_elem((3, 2, 4), false);
-  println!("{:?}", a);
-}
-```
-
-### Some common create helper functions
-`linspace` - Create a 1-D array with 21 elements with values 0., …, 5.
-```rust
-use ndarray::prelude::*;
-use ndarray::{Array, Ix3};
-fn main() {
-  let a = Array::<f64, _>::linspace(0., 5., 11);
-  println!("{:?}", a);
-}
-```
-The output is:
-```
-[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0], shape=[11], strides=[1], layout=C | F (0x3), const ndim=1
-```
-
-And there are also `range`, `logspace`, `ones`, `eye` and so on you can choose to use.
-
-## Basic operations
-
-```rust
-use ndarray::prelude::*;
-use ndarray::Array;
-use std::f64::INFINITY as inf;
-
-fn main() {
-    let a = array![
-                [10.,20.,30., 40.,], 
-            ];
-    let b = Array::range(0., 4., 1.);  // [0., 1., 2., 3, ]
-
-    assert_eq!(&a + &b, array![[10., 21., 32., 43.,]]);  // Allocates a new array. Note the explicit `&`.
-    assert_eq!(&a - &b, array![[10., 19., 28., 37.,]]);
-    assert_eq!(&a * &b, array![[0., 20., 60., 120.,]]);
-    assert_eq!(&a / &b, array![[inf, 20., 15., 13.333333333333334,]]);
-}
-```
-
-Try remove all the `&` sign in front of `a` and `b`, does it still compile? Why?
-
-Note that
-* `&A @ &A` produces a new `Array`
-* `B @ A` consumes `B`, updates it with the result, and returns it
-* `B @ &A` consumes `B`, updates it with the result, and returns it
-* `C @= &A` performs an arithmetic operation in place
-
-For more info checkout https://docs.rs/ndarray/latest/ndarray/struct.ArrayBase.html#arithmetic-operations
-
-Some operations have `_axis` appended to the function name: they generally take in a parameter of type `Axis` as one of their inputs,
-such as `sum_axis`:
-
-```rust
-use ndarray::{aview0, aview1, arr2, Axis};
-
-fn main() {
-    let a = arr2(&[[1., 2., 3.],
-                   [4., 5., 6.]]);
-    assert!(
-        a.sum_axis(Axis(0)) == aview1(&[5., 7., 9.]) &&
-        a.sum_axis(Axis(1)) == aview1(&[6., 15.]) &&
-
-        a.sum_axis(Axis(0)).sum_axis(Axis(0)) == aview0(&21.) &&
-        a.sum_axis(Axis(0)).sum_axis(Axis(0)) == aview0(&a.sum())
-    );
-}
-```
-
-### Matrix product
-
-```rust
-use ndarray::prelude::*;
-use ndarray::Array;
-
-fn main() {
-    let a = array![
-                [10.,20.,30., 40.,], 
-            ];
-    let b = Array::range(0., 4., 1.);     // b = [0., 1., 2., 3, ]
-    println!("a shape {:?}", &a.shape());
-    println!("b shape {:?}", &b.shape());
-    
-    let b = b.into_shape((4,1)).unwrap(); // reshape b to shape [4, 1]
-    println!("b shape {:?}", &b.shape());
-    
-    println!("{}", a.dot(&b));            // [1, 4] x [4, 1] -> [1, 1] 
-    println!("{}", a.t().dot(&b.t()));    // [4, 1] x [1, 4] -> [4, 4]
-}
-```
-The output is:
-```
-a shape [1, 4]
-b shape [4]
-b shape after reshape [4, 1]
-[[200]]
-[[0, 10, 20, 30],
- [0, 20, 40, 60],
- [0, 30, 60, 90],
- [0, 40, 80, 120]]
-```
-
-## Indexing, Slicing and Iterating
-One-dimensional arrays can be indexed, sliced and iterated over, much like `numpy` arrays
-
-```rust
-use ndarray::prelude::*;
-use ndarray::Array;
-
-fn main() {
-    let a = Array::range(0., 10., 1.);
-
-    let mut a = a.mapv(|a: f64| a.powi(3));  // numpy equivlant of `a ** 3`; https://doc.rust-lang.org/nightly/std/primitive.f64.html#method.powi
-
-    println!("{}", a);
-
-    println!("{}", a[[2]]);
-    println!("{}", a.slice(s![2]));
-
-    println!("{}", a.slice(s![2..5]));
-
-    a.slice_mut(s![..6;2]).fill(1000.);  // numpy equivlant of `a[:6:2] = 1000`
-    println!("{}", a);
-
-    for i in a.iter() {
-        print!("{}, ", i.powf(1./3.))
-    }
-}
-```
-The output is:
-```
-[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
-8
-8
-[8, 27, 64]
-[1000, 1, 1000, 27, 1000, 125, 216, 343, 512, 729]
-9.999999999999998, 1, 9.999999999999998, 3, 9.999999999999998, 4.999999999999999, 5.999999999999999, 6.999999999999999, 7.999999999999999, 8.999999999999998,
-```
-
-For more info about iteration see [Loops, Producers, and Iterators](https://docs.rs/ndarray/0.13.0/ndarray/struct.ArrayBase.html#loops-producers-and-iterators)
-
-Let's try a 3D array with elements of type `isize`. This is how you index it: 
-```rust
-use ndarray::prelude::*;
-
-fn main() {
-    let a = array![
-                    [[  0,  1,  2],         // a 3D array  2 x 2 x 3
-                     [ 10, 12, 13]],
-
-                    [[100,101,102],
-                     [110,112,113]]
-                ];
-
-    let a = a.mapv(|a: isize| a.pow(1));  // numpy equivlant of `a ** 1`; 
-                                          // This line does nothing but illustrate mapv with isize type 
-    println!("a -> \n{}\n", a);
-
-    println!("`a.slice(s![1, .., ..])` -> \n{}\n", a.slice(s![1, .., ..]));
-
-    println!("`a.slice(s![.., .., 2])` -> \n{}\n", a.slice(s![.., .., 2]));
-
-    println!("`a.slice(s![.., 1, 0..2])` -> \n{}\n", a.slice(s![.., 1, 0..2]));
-
-    println!("`a.iter()` ->");
-    for i in a.iter() {
-        print!("{}, ", i)  // flat out to every element
-    }
-
-    println!("\n\n`a.outer_iter()` ->");
-    for i in a.outer_iter() {
-        print!("row: {}, \n", i)  // iterate through first dimension
-    }
-}
-```
-The output is:
-```
-a -> 
-[[[0, 1, 2],
-  [10, 12, 13]],
-
- [[100, 101, 102],
-  [110, 112, 113]]]
-
-`a.slice(s![1, .., ..])` -> 
-[[100, 101, 102],
- [110, 112, 113]]
-
-`a.slice(s![.., .., 2])` -> 
-[[2, 13],
- [102, 113]]
-
-`a.slice(s![.., 1, 0..2])` -> 
-[[10, 12],
- [110, 112]]
-
-`a.iter()` ->
-0, 1, 2, 10, 12, 13, 100, 101, 102, 110, 112, 113, 
-
-`a.outer_iter()` ->
-row: [[0, 1, 2],
- [10, 12, 13]], 
-row: [[100, 101, 102],
- [110, 112, 113]], 
-```
-
-## Shape Manipulation
-
-### Changing the shape of an array
-The shape of an array can be changed with `into_shape` method.
-
-````rust
-use ndarray::prelude::*;
-use ndarray::Array;
-use std::iter::FromIterator;
-// use ndarray_rand::RandomExt;
-// use ndarray_rand::rand_distr::Uniform;
-
-fn main() {
-    // Or you may use ndarray_rand crate to generate random arrays
-    // let a = Array::random((2, 5), Uniform::new(0., 10.));
-    
-    let a = array![
-        [3., 7., 3., 4.],
-        [1., 4., 2., 2.],
-        [7., 2., 4., 9.]];
-        
-    println!("a = \n{:?}\n", a);
-    
-    // use trait FromIterator to flatten a matrix to a vector
-    let b = Array::from_iter(a.iter());
-    println!("b = \n{:?}\n", b);
-    
-    let c = b.into_shape([6, 2]).unwrap(); // consume b and generate c with new shape
-    println!("c = \n{:?}", c);
-}
-````
-The output is:
-```
-a = 
-[[3.0, 7.0, 3.0, 4.0],
- [1.0, 4.0, 2.0, 2.0],
- [7.0, 2.0, 4.0, 9.0]], shape=[3, 4], strides=[4, 1], layout=C (0x1), const ndim=2
-
-b = 
-[3.0, 7.0, 3.0, 4.0, 1.0, 4.0, 2.0, 2.0, 7.0, 2.0, 4.0, 9.0], shape=[12], strides=[1], layout=C | F (0x3), const ndim=1
-
-c = 
-[[3.0, 7.0],
- [3.0, 4.0],
- [1.0, 4.0],
- [2.0, 2.0],
- [7.0, 2.0],
- [4.0, 9.0]], shape=[6, 2], strides=[2, 1], layout=C (0x1), const ndim=2
-```
-
-### Stacking together different arrays
-
-Macro `stack!` is helpful for stacking arrays: 
-```rust
-use ndarray::prelude::*;
-use ndarray::{Array, Axis, stack};
-
-fn main() {
-
-    let a = array![
-        [9., 7.],
-        [5., 2.]];
-    
-    let b = array![
-        [1., 9.],
-        [5., 1.]];
-        
-    println!("a vstack b = \n{:?}\n", stack![Axis(0), a, b]);
-    
-    println!("a hstack b = \n{:?}\n", stack![Axis(1), a, b]);
-}
-```
-The output is:
-```
-a vstack b = 
-[[9.0, 7.0],
- [5.0, 2.0],
- [1.0, 9.0],
- [5.0, 1.0]], shape=[4, 2], strides=[2, 1], layout=C (0x1), const ndim=2
-
-a hstack b = 
-[[9.0, 7.0, 1.0, 9.0],
- [5.0, 2.0, 5.0, 1.0]], shape=[2, 4], strides=[4, 1], layout=C (0x1), const ndim=2
-```
-
-### Splitting one array into several smaller ones
-
-More to see here [ArrayView::split_at](https://docs.rs/ndarray/latest/ndarray/type.ArrayView.html#method.split_at)
-```rust
-use ndarray::prelude::*;
-use ndarray::Axis;
-
-fn main() {
-
-    let a = array![
-        [6., 7., 6., 9., 0., 5., 4., 0., 6., 8., 5., 2.],
-        [8., 5., 5., 7., 1., 8., 6., 7., 1., 8., 1., 0.]];
-    
-    let (s1, s2) = a.view().split_at(Axis(0), 1);
-    println!("Split a from Axis(0), at index 1:");
-    println!("s1  = \n{}", s1);
-    println!("s2  = \n{}\n", s2);
-    
-    
-    let (s1, s2) = a.view().split_at(Axis(1), 4);
-    println!("Split a from Axis(1), at index 4:");
-    println!("s1  = \n{}", s1);
-    println!("s2  = \n{}\n", s2);
-}
-```
-The output is:
-```
-Split a from Axis(0), at index 1:
-s1  = 
-[[6, 7, 6, 9, 0, 5, 4, 0, 6, 8, 5, 2]]
-s2  = 
-[[8, 5, 5, 7, 1, 8, 6, 7, 1, 8, 1, 0]]
-
-Split a from Axis(1), at index 4:
-s1  = 
-[[6, 7, 6, 9],
- [8, 5, 5, 7]]
-s2  = 
-[[0, 5, 4, 0, 6, 8, 5, 2],
- [1, 8, 6, 7, 1, 8, 1, 0]]
-
-```
-
-## Copies and Views
-### View, Ref or Shallow Copy
-As in Rust we have owner ship, so we cannot simply 
-update an element of an array while we have a 
-shared view of it. This will help us write more
-robust code.
-
-```rust
-use ndarray::prelude::*;
-use ndarray::{Array, Axis};
-
-fn main() {
-
-    let mut a = Array::range(0., 12., 1.).into_shape([3 ,4]).unwrap();
-    println!("a = \n{}\n", a);
-    
-    {
-        let (s1, s2) = a.view().split_at(Axis(1), 2);
-        
-        // with s as a view sharing the ref of a, we cannot update a here
-        // a.slice_mut(s![1, 1]).fill(1234.);
-        
-        println!("Split a from Axis(0), at index 1:");
-        println!("s1  = \n{}", s1);
-        println!("s2  = \n{}\n", s2);
-    }
-    
-    // now we can update a again here, as views of s1, s2 are dropped already
-    a.slice_mut(s![1, 1]).fill(1234.);
-    
-    let (s1, s2) = a.view().split_at(Axis(1), 2);
-    println!("Split a from Axis(0), at index 1:");
-    println!("s1  = \n{}", s1);
-    println!("s2  = \n{}\n", s2);
-}
-```
-The output is:
-```
-a = 
-[[0, 1, 2, 3],
- [4, 5, 6, 7],
- [8, 9, 10, 11]]
-
-Split a from Axis(0), at index 1:
-s1  = 
-[[0, 1],
- [4, 5],
- [8, 9]]
-s2  = 
-[[2, 3],
- [6, 7],
- [10, 11]]
-
-Split a from Axis(0), at index 1:
-s1  = 
-[[0, 1],
- [4, 1234],
- [8, 9]]
-s2  = 
-[[2, 3],
- [6, 7],
- [10, 11]]
-```
-
-### Deep Copy
-As the usual way in Rust, a `clone()` call will
-make a copy of your array:
-```rust
-use ndarray::prelude::*;
-use ndarray::Array;
-
-fn main() {
-
-    let mut a = Array::range(0., 4., 1.).into_shape([2 ,2]).unwrap();
-    let b = a.clone();
-    
-    println!("a = \n{}\n", a);
-    println!("b clone of a = \n{}\n", a);
-    
-    a.slice_mut(s![1, 1]).fill(1234.);
-    
-    println!("a updated...");
-    println!("a = \n{}\n", a);
-    println!("b clone of a = \n{}\n", b);
-}
-```
-
-The output is:
-```
-a = 
-[[0, 1],
- [2, 3]]
-
-b clone of a = 
-[[0, 1],
- [2, 3]]
-
-a updated...
-a = 
-[[0, 1],
- [2, 1234]]
-
-b clone of a = 
-[[0, 1],
- [2, 3]]
-```
-
-Noticing that using `clone()` (or cloning) an `Array` type also copies the array's elements. It creates an independently owned array of the same type.
-
-Cloning an `ArrayView` does not clone or copy the underlying elements - it just clones the view reference (as it happens in Rust when cloning a `&` reference).
-
-## Broadcasting
-
-Arrays support limited broadcasting, where arithmetic operations with array operands of different sizes can be carried out by repeating the elements of the smaller dimension array. 
-
-```rust
-use ndarray::prelude::*;
-
-fn main() {
-    let a = array![
-        [1., 1.], 
-        [1., 2.], 
-        [0., 3.], 
-        [0., 4.]];
-
-    let b = array![[0., 1.]];
-
-    let c = array![
-        [1., 2.], 
-        [1., 3.], 
-        [0., 4.], 
-        [0., 5.]];
-    
-    // We can add because the shapes are compatible even if not equal.
-    // The `b` array is shape 1 × 2 but acts like a 4 × 2 array.
-    assert!(c == a + b);
-}
-```
-
-See [.broadcast()](https://docs.rs/ndarray/latest/ndarray/struct.ArrayBase.html#method.broadcast) for a more detailed description.
-
-And there is a short example of it:
-```rust
-use ndarray::prelude::*;
-
-fn main() {
-    let a = array![
-        [1., 2.],
-        [3., 4.],
-    ];
-    
-    let b =  a.broadcast((3, 2, 2)).unwrap();
-    println!("shape of a is {:?}", a.shape());
-    println!("a is broadcased to 3x2x2 = \n{}", b);
-}
-```
-The output is:
-```
-shape of a is [2, 2]
-a is broadcased to 3x2x2 = 
-[[[1, 2],
-  [3, 4]],
-
- [[1, 2],
-  [3, 4]],
-
- [[1, 2],
-  [3, 4]]]
-```
-
-## Want to learn more?
-Please checkout these docs for more information
-* [`ArrayBase` doc page](https://docs.rs/ndarray/latest/ndarray/struct.ArrayBase.html)
-* [`ndarray` for `numpy` user doc page](https://docs.rs/ndarray/latest/ndarray/doc/ndarray_for_numpy_users/index.html)
diff --git a/README.rst b/README.rst
index 9e2580a7..f1a3524a 100644
--- a/README.rst
+++ b/README.rst
@@ -5,7 +5,6 @@ The ``ndarray`` crate provides an *n*-dimensional container for general elements
 and for numerics.
 
 Please read the `API documentation on docs.rs`__
-or take a look at the `quickstart tutorial <./README-quick-start.md>`_.
 
 __ https://docs.rs/ndarray/
 
diff --git a/RELEASES.md b/RELEASES.md
index b5e351e9..071d1246 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,4 +1,4 @@
-Version 0.13.1 (2020-04-21)
+Version 0.13.1 (2020-04)
 ===========================
 
 New features
@@ -14,10 +14,7 @@ New features
   https://github.com/rust-ndarray/ndarray/pull/728
 - `Dimension::Larger` now requires `RemoveAxis` by [@TheLortex]
   https://github.com/rust-ndarray/ndarray/pull/792
-- New methods for collecting Zip into an array by [@bluss]
-  https://github.com/rust-ndarray/ndarray/pull/797
-- New `Array::maybe_uninit` and `.assume_init()` by [@bluss]
-  https://github.com/rust-ndarray/ndarray/pull/803
+
 
 Enhancements
 ------------
@@ -28,8 +25,6 @@ Enhancements
   https://github.com/rust-ndarray/ndarray/pull/754
 - Implement `fold` for `IndicesIter` by [@jturner314]
   https://github.com/rust-ndarray/ndarray/pull/733
-- New Quick Start readme by [@lifuyang]
-  https://github.com/rust-ndarray/ndarray/pull/785
 
 API changes
 -----------
@@ -44,9 +39,6 @@ Other changes
 - Improve blas version documentation by [@jturner314]
 - Doc improvements by [@mockersf] https://github.com/rust-ndarray/ndarray/pull/751
 - Doc and lint related improvements by [@viniciusd] https://github.com/rust-ndarray/ndarray/pull/750
-- Minor fixes related to best practices for unsafe code by [@bluss]
-  https://github.com/rust-ndarray/ndarray/pull/799
-  https://github.com/rust-ndarray/ndarray/pull/802
 - Release management by [@bluss]
 
 
@@ -952,4 +944,3 @@ Earlier releases
 [@TheLortex]: https://github.com/TheLortex
 [@mockersf]: https://github.com/mockersf
 [@viniciusd]: https://github.com/viniciusd
-[@lifuyang]: https://github.com/liufuyang
diff --git a/benches/bench1.rs b/benches/bench1.rs
index 8cd04c45..d7fa9f16 100644
--- a/benches/bench1.rs
+++ b/benches/bench1.rs
@@ -286,38 +286,6 @@ fn add_2d_alloc_zip_collect(bench: &mut test::Bencher) {
     });
 }
 
-#[bench]
-fn vec_string_collect(bench: &mut test::Bencher) {
-    let v = vec![""; 10240];
-    bench.iter(|| {
-        v.iter().map(|s| s.to_owned()).collect::<Vec<_>>()
-    });
-}
-
-#[bench]
-fn array_string_collect(bench: &mut test::Bencher) {
-    let v = Array::from(vec![""; 10240]);
-    bench.iter(|| {
-        Zip::from(&v).apply_collect(|s| s.to_owned())
-    });
-}
-
-#[bench]
-fn vec_f64_collect(bench: &mut test::Bencher) {
-    let v = vec![1.; 10240];
-    bench.iter(|| {
-        v.iter().map(|s| s + 1.).collect::<Vec<_>>()
-    });
-}
-
-#[bench]
-fn array_f64_collect(bench: &mut test::Bencher) {
-    let v = Array::from(vec![1.; 10240]);
-    bench.iter(|| {
-        Zip::from(&v).apply_collect(|s| s + 1.)
-    });
-}
-
 
 #[bench]
 fn add_2d_assign_ops(bench: &mut test::Bencher) {
diff --git a/benches/iter.rs b/benches/iter.rs
index 8b89e721..24bda95b 100644
--- a/benches/iter.rs
+++ b/benches/iter.rs
@@ -381,12 +381,12 @@ pub fn zip_mut_with(data: &Array3<f32>, out: &mut Array3<f32>) {
 fn zip_mut_with_cc(b: &mut Bencher) {
     let data: Array3<f32> = Array3::zeros((ISZ, ISZ, ISZ));
     let mut out = Array3::zeros(data.dim());
-    b.iter(|| zip_mut_with(&data, &mut out));
+    b.iter(|| black_box(zip_mut_with(&data, &mut out)));
 }
 
 #[bench]
 fn zip_mut_with_ff(b: &mut Bencher) {
     let data: Array3<f32> = Array3::zeros((ISZ, ISZ, ISZ).f());
     let mut out = Array3::zeros(data.dim().f());
-    b.iter(|| zip_mut_with(&data, &mut out));
+    b.iter(|| black_box(zip_mut_with(&data, &mut out)));
 }
diff --git a/benches/par_rayon.rs b/benches/par_rayon.rs
index cca35e00..e8c4cfef 100644
--- a/benches/par_rayon.rs
+++ b/benches/par_rayon.rs
@@ -136,39 +136,3 @@ fn rayon_add(bench: &mut Bencher) {
         });
     });
 }
-
-const COLL_STRING_N: usize = 64;
-const COLL_F64_N: usize = 128;
-
-#[bench]
-fn vec_string_collect(bench: &mut test::Bencher) {
-    let v = vec![""; COLL_STRING_N * COLL_STRING_N];
-    bench.iter(|| {
-        v.iter().map(|s| s.to_owned()).collect::<Vec<_>>()
-    });
-}
-
-#[bench]
-fn array_string_collect(bench: &mut test::Bencher) {
-    let v = Array::from_elem((COLL_STRING_N, COLL_STRING_N), "");
-    bench.iter(|| {
-        Zip::from(&v).par_apply_collect(|s| s.to_owned())
-    });
-}
-
-#[bench]
-fn vec_f64_collect(bench: &mut test::Bencher) {
-    let v = vec![1.; COLL_F64_N * COLL_F64_N];
-    bench.iter(|| {
-        v.iter().map(|s| s + 1.).collect::<Vec<_>>()
-    });
-}
-
-#[bench]
-fn array_f64_collect(bench: &mut test::Bencher) {
-    let v = Array::from_elem((COLL_F64_N, COLL_F64_N), 1.);
-    bench.iter(|| {
-        Zip::from(&v).par_apply_collect(|s| s + 1.)
-    });
-}
-
diff --git a/benches/zip.rs b/benches/zip.rs
deleted file mode 100644
index ac7bef58..00000000
--- a/benches/zip.rs
+++ /dev/null
@@ -1,120 +0,0 @@
-#![feature(test)]
-extern crate test;
-use test::{black_box, Bencher};
-use ndarray::{Array3, ShapeBuilder, Zip};
-use ndarray::s;
-use ndarray::IntoNdProducer;
-
-pub fn zip_copy<'a, A, P, Q>(data: P, out: Q)
-    where P: IntoNdProducer<Item = &'a A>,
-          Q: IntoNdProducer<Item = &'a mut A, Dim = P::Dim>,
-          A: Copy + 'a
-{
-    Zip::from(data).and(out).apply(|&i, o| {
-        *o = i;
-    });
-}
-
-pub fn zip_copy_split<'a, A, P, Q>(data: P, out: Q)
-    where P: IntoNdProducer<Item = &'a A>,
-          Q: IntoNdProducer<Item = &'a mut A, Dim = P::Dim>,
-          A: Copy + 'a
-{
-    let z = Zip::from(data).and(out);
-    let (z1, z2) = z.split();
-    let (z11, z12) = z1.split();
-    let (z21, z22) = z2.split();
-    let f = |&i: &A, o: &mut A| *o = i;
-    z11.apply(f);
-    z12.apply(f);
-    z21.apply(f);
-    z22.apply(f);
-}
-
-pub fn zip_indexed(data: &Array3<f32>, out: &mut Array3<f32>) {
-    Zip::indexed(data).and(out).apply(|idx, &i, o| {
-        let _ = black_box(idx);
-        *o = i;
-    });
-}
-
-// array size in benchmarks
-const SZ3: (usize, usize, usize) = (100, 110, 100);
-
-#[bench]
-fn zip_cc(b: &mut Bencher) {
-    let data: Array3<f32> = Array3::zeros(SZ3);
-    let mut out = Array3::zeros(data.dim());
-    b.iter(|| zip_copy(&data, &mut out));
-}
-
-#[bench]
-fn zip_cf(b: &mut Bencher) {
-    let data: Array3<f32> = Array3::zeros(SZ3);
-    let mut out = Array3::zeros(data.dim().f());
-    b.iter(|| zip_copy(&data, &mut out));
-}
-
-#[bench]
-fn zip_fc(b: &mut Bencher) {
-    let data: Array3<f32> = Array3::zeros(SZ3.f());
-    let mut out = Array3::zeros(data.dim());
-    b.iter(|| zip_copy(&data, &mut out));
-}
-
-#[bench]
-fn zip_ff(b: &mut Bencher) {
-    let data: Array3<f32> = Array3::zeros(SZ3.f());
-    let mut out = Array3::zeros(data.dim().f());
-    b.iter(|| zip_copy(&data, &mut out));
-}
-
-#[bench]
-fn zip_indexed_cc(b: &mut Bencher) {
-    let data: Array3<f32> = Array3::zeros(SZ3);
-    let mut out = Array3::zeros(data.dim());
-    b.iter(|| zip_indexed(&data, &mut out));
-}
-
-#[bench]
-fn zip_indexed_ff(b: &mut Bencher) {
-    let data: Array3<f32> = Array3::zeros(SZ3.f());
-    let mut out = Array3::zeros(data.dim().f());
-    b.iter(|| zip_indexed(&data, &mut out));
-}
-
-#[bench]
-fn slice_zip_cc(b: &mut Bencher) {
-    let data: Array3<f32> = Array3::zeros(SZ3);
-    let mut out = Array3::zeros(data.dim());
-    let data = data.slice(s![1.., 1.., 1..]);
-    let mut out = out.slice_mut(s![1.., 1.., 1..]);
-    b.iter(|| zip_copy(&data, &mut out));
-}
-
-#[bench]
-fn slice_zip_ff(b: &mut Bencher) {
-    let data: Array3<f32> = Array3::zeros(SZ3.f());
-    let mut out = Array3::zeros(data.dim().f());
-    let data = data.slice(s![1.., 1.., 1..]);
-    let mut out = out.slice_mut(s![1.., 1.., 1..]);
-    b.iter(|| zip_copy(&data, &mut out));
-}
-
-#[bench]
-fn slice_split_zip_cc(b: &mut Bencher) {
-    let data: Array3<f32> = Array3::zeros(SZ3);
-    let mut out = Array3::zeros(data.dim());
-    let data = data.slice(s![1.., 1.., 1..]);
-    let mut out = out.slice_mut(s![1.., 1.., 1..]);
-    b.iter(|| zip_copy_split(&data, &mut out));
-}
-
-#[bench]
-fn slice_split_zip_ff(b: &mut Bencher) {
-    let data: Array3<f32> = Array3::zeros(SZ3.f());
-    let mut out = Array3::zeros(data.dim().f());
-    let data = data.slice(s![1.., 1.., 1..]);
-    let mut out = out.slice_mut(s![1.., 1.., 1..]);
-    b.iter(|| zip_copy_split(&data, &mut out));
-}
diff --git a/build.rs b/build.rs
index dd06c0b8..ceeeff38 100644
--- a/build.rs
+++ b/build.rs
@@ -5,6 +5,6 @@
 fn main() {
     println!("cargo:rerun-if-changed=build.rs");
     if cfg!(feature = "test-blas-openblas-sys") {
-        println!("cargo:rustc-link-lib=dylib=openblas");
+        println!("cargo:rustc-link-lib={}=openblas", "dylib");
     }
 }
diff --git a/clippy.toml b/clippy.toml
deleted file mode 100644
index 5ef4300e..00000000
--- a/clippy.toml
+++ /dev/null
@@ -1,1 +0,0 @@
-single-char-binding-names-threshold = 1000
diff --git a/src/dimension/dimension_trait.rs b/src/dimension/dimension_trait.rs
index b2ef92e4..4f03d3aa 100644
--- a/src/dimension/dimension_trait.rs
+++ b/src/dimension/dimension_trait.rs
@@ -540,14 +540,6 @@ impl Dimension for Dim<[Ix; 1]> {
     fn try_remove_axis(&self, axis: Axis) -> Self::Smaller {
         self.remove_axis(axis)
     }
-
-    fn from_dimension<D2: Dimension>(d: &D2) -> Option<Self> {
-        if 1 == d.ndim() {
-            Some(Ix1(d[0]))
-        } else {
-            None
-        }
-    }
     private_impl! {}
 }
 
diff --git a/src/dimension/mod.rs b/src/dimension/mod.rs
index 212197fe..28d2e9b2 100644
--- a/src/dimension/mod.rs
+++ b/src/dimension/mod.rs
@@ -139,14 +139,6 @@ pub fn can_index_slice_not_custom<A, D: Dimension>(data: &[A], dim: &D) -> Resul
 ///    also implies that the length of any individual axis does not exceed
 ///    `isize::MAX`.)
 pub fn max_abs_offset_check_overflow<A, D>(dim: &D, strides: &D) -> Result<usize, ShapeError>
-where
-    D: Dimension,
-{
-    max_abs_offset_check_overflow_impl(mem::size_of::<A>(), dim, strides)
-}
-
-fn max_abs_offset_check_overflow_impl<D>(elem_size: usize, dim: &D, strides: &D)
-    -> Result<usize, ShapeError>
 where
     D: Dimension,
 {
@@ -176,7 +168,7 @@ where
     // Determine absolute difference in units of bytes between least and
     // greatest address accessible by moving along all axes
     let max_offset_bytes = max_offset
-        .checked_mul(elem_size)
+        .checked_mul(mem::size_of::<A>())
         .ok_or_else(|| from_kind(ErrorKind::Overflow))?;
     // Condition 2b.
     if max_offset_bytes > isize::MAX as usize {
@@ -224,21 +216,13 @@ pub fn can_index_slice<A, D: Dimension>(
 ) -> Result<(), ShapeError> {
     // Check conditions 1 and 2 and calculate `max_offset`.
     let max_offset = max_abs_offset_check_overflow::<A, _>(dim, strides)?;
-    can_index_slice_impl(max_offset, data.len(), dim, strides)
-}
 
-fn can_index_slice_impl<D: Dimension>(
-    max_offset: usize,
-    data_len: usize,
-    dim: &D,
-    strides: &D,
-) -> Result<(), ShapeError> {
     // Check condition 4.
     let is_empty = dim.slice().iter().any(|&d| d == 0);
-    if is_empty && max_offset > data_len {
+    if is_empty && max_offset > data.len() {
         return Err(from_kind(ErrorKind::OutOfBounds));
     }
-    if !is_empty && max_offset >= data_len {
+    if !is_empty && max_offset >= data.len() {
         return Err(from_kind(ErrorKind::OutOfBounds));
     }
 
diff --git a/src/doc/ndarray_for_numpy_users/coord_transform.rs b/src/doc/ndarray_for_numpy_users/coord_transform.rs
index e019fd1b..ef922f2a 100644
--- a/src/doc/ndarray_for_numpy_users/coord_transform.rs
+++ b/src/doc/ndarray_for_numpy_users/coord_transform.rs
@@ -49,6 +49,8 @@
 //! This is a direct translation to `ndarray`:
 //!
 //! ```
+//! extern crate ndarray;
+//!
 //! use ndarray::prelude::*;
 //!
 //! fn main() {
@@ -94,6 +96,8 @@
 //! this:
 //!
 //! ```
+//! extern crate ndarray;
+//!
 //! use ndarray::prelude::*;
 //!
 //! fn main() {
diff --git a/src/doc/ndarray_for_numpy_users/mod.rs b/src/doc/ndarray_for_numpy_users/mod.rs
index 6469e4f5..d3125e71 100644
--- a/src/doc/ndarray_for_numpy_users/mod.rs
+++ b/src/doc/ndarray_for_numpy_users/mod.rs
@@ -177,6 +177,8 @@
 //! and `ndarray` like this:
 //!
 //! ```
+//! extern crate ndarray;
+//!
 //! use ndarray::prelude::*;
 //! #
 //! # fn main() {}
@@ -531,6 +533,7 @@
 //! `a[:] = 3.` | [`a.fill(3.)`][.fill()] | set all array elements to the same scalar value
 //! `a[:] = b` | [`a.assign(&b)`][.assign()] | copy the data from array `b` into array `a`
 //! `np.concatenate((a,b), axis=1)` | [`stack![Axis(1), a, b]`][stack!] or [`stack(Axis(1), &[a.view(), b.view()])`][stack()] | concatenate arrays `a` and `b` along axis 1
+//! `np.stack((a,b), axis=1)` | [`stack_new_axis![Axis(1), a, b]`][stack_new_axis!] or [`stack_new_axis(Axis(1), vec![a.view(), b.view()])`][stack_new_axis()] | stack arrays `a` and `b` along axis 1
 //! `a[:,np.newaxis]` or `np.expand_dims(a, axis=1)` | [`a.insert_axis(Axis(1))`][.insert_axis()] | create an array from `a`, inserting a new axis 1
 //! `a.transpose()` or `a.T` | [`a.t()`][.t()] or [`a.reversed_axes()`][.reversed_axes()] | transpose of array `a` (view for `.t()` or by-move for `.reversed_axes()`)
 //! `np.diag(a)` | [`a.diag()`][.diag()] | view the diagonal of `a`
@@ -640,6 +643,8 @@
 //! [.shape()]: ../../struct.ArrayBase.html#method.shape
 //! [stack!]: ../../macro.stack.html
 //! [stack()]: ../../fn.stack.html
+//! [stack_new_axis!]: ../../macro.stack_new_axis.html
+//! [stack_new_axis()]: ../../fn.stack_new_axis.html
 //! [.strides()]: ../../struct.ArrayBase.html#method.strides
 //! [.index_axis()]: ../../struct.ArrayBase.html#method.index_axis
 //! [.sum_axis()]: ../../struct.ArrayBase.html#method.sum_axis
diff --git a/src/doc/ndarray_for_numpy_users/rk_step.rs b/src/doc/ndarray_for_numpy_users/rk_step.rs
index 2c5ce236..a68f6b17 100644
--- a/src/doc/ndarray_for_numpy_users/rk_step.rs
+++ b/src/doc/ndarray_for_numpy_users/rk_step.rs
@@ -71,6 +71,8 @@
 //! A direct translation to `ndarray` looks like this:
 //!
 //! ```
+//! extern crate ndarray;
+//!
 //! use ndarray::prelude::*;
 //!
 //! fn rk_step<F>(
@@ -127,6 +129,8 @@
 //!   some places, but that's not demonstrated in the example below.
 //!
 //! ```
+//! extern crate ndarray;
+//!
 //! use ndarray::prelude::*;
 //!
 //! fn rk_step<F>(
diff --git a/src/doc/ndarray_for_numpy_users/simple_math.rs b/src/doc/ndarray_for_numpy_users/simple_math.rs
index 1bba0c28..b6549d3c 100644
--- a/src/doc/ndarray_for_numpy_users/simple_math.rs
+++ b/src/doc/ndarray_for_numpy_users/simple_math.rs
@@ -51,6 +51,8 @@
 //! <td>
 //!
 //! ```
+//! extern crate ndarray;
+//!
 //! use ndarray::prelude::*;
 //!
 //! # fn main() {
diff --git a/src/impl_2d.rs b/src/impl_2d.rs
index 7b919eb3..f44990ed 100644
--- a/src/impl_2d.rs
+++ b/src/impl_2d.rs
@@ -17,12 +17,6 @@ where
     /// Return an array view of row `index`.
     ///
     /// **Panics** if `index` is out of bounds.
-    ///
-    /// ```
-    /// use ndarray::array;
-    /// let array = array![[1., 2.], [3., 4.]];
-    /// assert_eq!(array.row(0), array![1., 2.]);
-    /// ```
     pub fn row(&self, index: Ix) -> ArrayView1<'_, A>
     where
         S: Data,
@@ -33,13 +27,6 @@ where
     /// Return a mutable array view of row `index`.
     ///
     /// **Panics** if `index` is out of bounds.
-    ///
-    /// ```
-    /// use ndarray::array;
-    /// let mut array = array![[1., 2.], [3., 4.]];
-    /// array.row_mut(0)[1] = 5.;
-    /// assert_eq!(array, array![[1., 5.], [3., 4.]]);
-    /// ```
     pub fn row_mut(&mut self, index: Ix) -> ArrayViewMut1<'_, A>
     where
         S: DataMut,
@@ -48,12 +35,6 @@ where
     }
 
     /// Return the number of rows (length of `Axis(0)`) in the two-dimensional array.
-    ///
-    /// ```
-    /// use ndarray::array;
-    /// let array = array![[1., 2.], [3., 4.]];
-    /// assert_eq!(array.nrows(), 2usize);
-    /// ```
     pub fn nrows(&self) -> usize {
         self.len_of(Axis(0))
     }
@@ -67,12 +48,6 @@ where
     /// Return an array view of column `index`.
     ///
     /// **Panics** if `index` is out of bounds.
-    ///
-    /// ```
-    /// use ndarray::array;
-    /// let array = array![[1., 2.], [3., 4.]];
-    /// assert_eq!(array.column(0), array![1., 3.]);
-    /// ```
     pub fn column(&self, index: Ix) -> ArrayView1<'_, A>
     where
         S: Data,
@@ -83,13 +58,6 @@ where
     /// Return a mutable array view of column `index`.
     ///
     /// **Panics** if `index` is out of bounds.
-    ///
-    /// ```
-    /// use ndarray::array;
-    /// let mut array = array![[1., 2.], [3., 4.]];
-    /// array.column_mut(0)[1] = 5.;
-    /// assert_eq!(array, array![[1., 2.], [5., 4.]]);
-    /// ```
     pub fn column_mut(&mut self, index: Ix) -> ArrayViewMut1<'_, A>
     where
         S: DataMut,
@@ -98,12 +66,6 @@ where
     }
 
     /// Return the number of columns (length of `Axis(1)`) in the two-dimensional array.
-    ///
-    /// ```
-    /// use ndarray::array;
-    /// let array = array![[1., 2.], [3., 4.]];
-    /// assert_eq!(array.ncols(), 2usize);
-    /// ```
     pub fn ncols(&self) -> usize {
         self.len_of(Axis(1))
     }
@@ -115,20 +77,6 @@ where
     }
 
     /// Return true if the array is square, false otherwise.
-    ///
-    /// # Examples
-    /// Sqaure:
-    /// ```
-    /// use ndarray::array;
-    /// let array = array![[1., 2.], [3., 4.]];
-    /// assert!(array.is_square());
-    /// ```
-    /// Not sqaure:
-    /// ```
-    /// use ndarray::array;
-    /// let array = array![[1., 2., 5.], [3., 4., 6.]];
-    /// assert!(!array.is_square());
-    /// ```
     pub fn is_square(&self) -> bool {
         self.nrows() == self.ncols()
     }
diff --git a/src/impl_constructors.rs b/src/impl_constructors.rs
index 8d1471e7..964003dd 100644
--- a/src/impl_constructors.rs
+++ b/src/impl_constructors.rs
@@ -474,20 +474,41 @@ where
     ///
     /// ### Safety
     ///
-    /// Accessing uninitalized values is undefined behaviour. You must overwrite *all* the elements
-    /// in the array after it is created; for example using
-    /// [`raw_view_mut`](ArrayBase::raw_view_mut) or other low-level element access.
+    /// Accessing uninitalized values is undefined behaviour. You must
+    /// overwrite *all* the elements in the array after it is created; for
+    /// example using the methods `.fill()` or `.assign()`.
     ///
     /// The contents of the array is indeterminate before initialization and it
     /// is an error to perform operations that use the previous values. For
     /// example it would not be legal to use `a += 1.;` on such an array.
     ///
     /// This constructor is limited to elements where `A: Copy` (no destructors)
-    /// to avoid users shooting themselves too hard in the foot.
-    /// 
-    /// (Also note that the constructors `from_shape_vec` and
-    /// `from_shape_vec_unchecked` allow the user yet more control, in the sense
-    /// that Arrays can be created from arbitrary vectors.)
+    /// to avoid users shooting themselves too hard in the foot; it is not
+    /// a problem to drop an array created with this method even before elements
+    /// are initialized. (Note that constructors `from_shape_vec` and
+    /// `from_shape_vec_unchecked` allow the user yet more control).
+    ///
+    /// ### Examples
+    ///
+    /// ```
+    /// use ndarray::{s, Array2};
+    ///
+    /// // Example Task: Let's create a column shifted copy of a in b
+    ///
+    /// fn shift_by_two(a: &Array2<f32>) -> Array2<f32> {
+    ///     let mut b = unsafe { Array2::uninitialized(a.dim()) };
+    ///
+    ///     // two first columns in b are two last in a
+    ///     // rest of columns in b are the initial columns in a
+    ///     b.slice_mut(s![.., ..2]).assign(&a.slice(s![.., -2..]));
+    ///     b.slice_mut(s![.., 2..]).assign(&a.slice(s![.., ..-2]));
+    ///
+    ///     // `b` is safe to use with all operations at this point
+    ///     b
+    /// }
+    ///
+    /// # shift_by_two(&Array2::zeros((8, 8)));
+    /// ```
     pub unsafe fn uninitialized<Sh>(shape: Sh) -> Self
     where
         A: Copy,
@@ -533,7 +554,7 @@ where
     /// use ndarray::Zip;
     /// use ndarray::Axis;
     ///
-    /// // Example Task: Let's create a column shifted copy of the input
+    /// // Example Task: Let's create a transposed copy of the input
     ///
     /// fn shift_by_two(a: &Array2<f32>) -> Array2<f32> {
     ///     // create an uninitialized array
@@ -553,8 +574,6 @@ where
     ///
     /// 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>,
diff --git a/src/impl_methods.rs b/src/impl_methods.rs
index 8a906be3..9853a1c4 100644
--- a/src/impl_methods.rs
+++ b/src/impl_methods.rs
@@ -28,7 +28,7 @@ use crate::iter::{
     IndexedIter, IndexedIterMut, Iter, IterMut, Lanes, LanesMut, Windows,
 };
 use crate::slice::MultiSlice;
-use crate::stacking::stack;
+use crate::stacking::concatenate;
 use crate::{NdIndex, Slice, SliceInfo, SliceOrIndex};
 
 /// # Methods For All Array Types
@@ -840,7 +840,7 @@ where
             dim.set_axis(axis, 0);
             unsafe { Array::from_shape_vec_unchecked(dim, vec![]) }
         } else {
-            stack(axis, &subs).unwrap()
+            concatenate(axis, &subs).unwrap()
         }
     }
 
@@ -1293,11 +1293,7 @@ where
         is_standard_layout(&self.dim, &self.strides)
     }
 
-    /// Return true if the array is known to be contiguous.
-    ///
-    /// Will detect c- and f-contig arrays correctly, but otherwise
-    /// There are some false negatives.
-    pub(crate) fn is_contiguous(&self) -> bool {
+    fn is_contiguous(&self) -> bool {
         D::is_contiguous(&self.dim, &self.strides)
     }
 
diff --git a/src/impl_views/splitting.rs b/src/impl_views/splitting.rs
index 38d07594..dcfb04b8 100644
--- a/src/impl_views/splitting.rs
+++ b/src/impl_views/splitting.rs
@@ -51,7 +51,7 @@ where
     /// ```rust
     /// # use ndarray::prelude::*;
     /// # let a = aview2(&[[0; 4]; 3]);
-    /// let (v1, v2) = a.split_at(Axis(0), 2);
+    /// let (v1, v2) = a.split_at(Axis(0), 1);
     /// ```
     /// ```text
     ///         ┌─────┬─────┬─────┬─────┐       0  ↓ indices
diff --git a/src/indexes.rs b/src/indexes.rs
index 6f45fb3d..9eb7a8b5 100644
--- a/src/indexes.rs
+++ b/src/indexes.rs
@@ -7,8 +7,7 @@
 // except according to those terms.
 use super::Dimension;
 use crate::dimension::IntoDimension;
-use crate::zip::Offset;
-use crate::split_at::SplitAt;
+use crate::zip::{Offset, Splittable};
 use crate::Axis;
 use crate::Layout;
 use crate::NdProducer;
@@ -158,20 +157,6 @@ where
     private_impl! {}
 }
 
-// How the NdProducer for Indices works.
-//
-// NdProducer allows for raw pointers (Ptr), strides (Stride) and the produced
-// item (Item).
-//
-// Instead of Ptr, there is `IndexPtr<D>` which is an index value, like [0, 0, 0]
-// for the three dimensional case.
-//
-// The stride is simply which axis is currently being incremented. The stride for axis 1, is 1.
-//
-// .stride_offset(stride, index) simply computes the new index along that axis, for example:
-// [0, 0, 0].stride_offset(1, 10) => [0, 10, 0]  axis 1 is incremented by 10.
-//
-// .as_ref() converts the Ptr value to an Item. For example [0, 10, 0] => (0, 10, 0)
 impl<D: Dimension + Copy> NdProducer for Indices<D> {
     type Item = D::Pattern;
     type Dim = D;
diff --git a/src/iterators/mod.rs b/src/iterators/mod.rs
index 621c141f..09111936 100644
--- a/src/iterators/mod.rs
+++ b/src/iterators/mod.rs
@@ -79,18 +79,15 @@ impl<A, D: Dimension> Iterator for Baseiter<A, D> {
         let ndim = self.dim.ndim();
         debug_assert_ne!(ndim, 0);
         let mut accum = init;
-        while let Some(mut index) = self.index {
+        while let Some(mut index) = self.index.clone() {
             let stride = self.strides.last_elem() as isize;
             let elem_index = index.last_elem();
             let len = self.dim.last_elem();
             let offset = D::stride_offset(&index, &self.strides);
             unsafe {
                 let row_ptr = self.ptr.offset(offset);
-                let mut i = 0;
-                let i_end = len - elem_index;
-                while i < i_end {
+                for i in 0..(len - elem_index) {
                     accum = g(accum, row_ptr.offset(i as isize * stride));
-                    i += 1;
                 }
             }
             index.set_last_elem(len - 1);
diff --git a/src/layout/layoutfmt.rs b/src/layout/layoutfmt.rs
index 3d7fad00..d5049512 100644
--- a/src/layout/layoutfmt.rs
+++ b/src/layout/layoutfmt.rs
@@ -8,7 +8,7 @@
 
 use super::Layout;
 
-const LAYOUT_NAMES: &[&str] = &["C", "F", "c", "f"];
+const LAYOUT_NAMES: &[&str] = &["C", "F"];
 
 use std::fmt;
 
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 24dd0995..57983cc3 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -1,8 +1,6 @@
 mod layoutfmt;
 
-// Layout it a bitset used for internal layout description of
-// arrays, producers and sets of producers.
-// The type is public but users don't interact with it.
+// public struct but users don't interact with it
 #[doc(hidden)]
 /// Memory layout description
 #[derive(Copy, Clone)]
@@ -10,136 +8,47 @@ pub struct Layout(u32);
 
 impl Layout {
     #[inline(always)]
-    pub(crate) fn is(self, flag: u32) -> bool {
-        self.0 & flag != 0
-    }
-
-    /// Return layout common to both inputs
-    #[inline(always)]
-    pub(crate) fn intersect(self, other: Layout) -> Layout {
-        Layout(self.0 & other.0)
+    pub(crate) fn new(x: u32) -> Self {
+        Layout(x)
     }
 
-    /// Return a layout that simultaneously "is" what both of the inputs are
     #[inline(always)]
-    pub(crate) fn also(self, other: Layout) -> Layout {
-        Layout(self.0 | other.0)
+    pub(crate) fn is(self, flag: u32) -> bool {
+        self.0 & flag != 0
     }
-
     #[inline(always)]
-    pub(crate) fn one_dimensional() -> Layout {
-        Layout::c().also(Layout::f())
+    pub(crate) fn and(self, flag: Layout) -> Layout {
+        Layout(self.0 & flag.0)
     }
 
     #[inline(always)]
-    pub(crate) fn c() -> Layout {
-        Layout(CORDER | CPREFER)
+    pub(crate) fn flag(self) -> u32 {
+        self.0
     }
+}
 
+impl Layout {
+    #[doc(hidden)]
     #[inline(always)]
-    pub(crate) fn f() -> Layout {
-        Layout(FORDER | FPREFER)
+    pub fn one_dimensional() -> Layout {
+        Layout(CORDER | FORDER)
     }
-
+    #[doc(hidden)]
     #[inline(always)]
-    pub(crate) fn cpref() -> Layout {
-        Layout(CPREFER)
+    pub fn c() -> Layout {
+        Layout(CORDER)
     }
-
+    #[doc(hidden)]
     #[inline(always)]
-    pub(crate) fn fpref() -> Layout {
-        Layout(FPREFER)
+    pub fn f() -> Layout {
+        Layout(FORDER)
     }
-
     #[inline(always)]
-    pub(crate) fn none() -> Layout {
+    #[doc(hidden)]
+    pub fn none() -> Layout {
         Layout(0)
     }
-
-    /// A simple "score" method which scores positive for preferring C-order, negative for F-order
-    /// Subject to change when we can describe other layouts
-    pub(crate) fn tendency(self) -> i32 {
-        (self.is(CORDER) as i32 - self.is(FORDER) as i32) +
-        (self.is(CPREFER) as i32 - self.is(FPREFER) as i32)
-
-    }
 }
 
 pub const CORDER: u32 = 0b01;
 pub const FORDER: u32 = 0b10;
-pub const CPREFER: u32 = 0b0100;
-pub const FPREFER: u32 = 0b1000;
-
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::imp_prelude::*;
-    use crate::NdProducer;
-
-    type M = Array2<f32>;
-
-    #[test]
-    fn contig_layouts() {
-        let a = M::zeros((5, 5));
-        let b = M::zeros((5, 5).f());
-        let ac = a.view().layout();
-        let af = b.view().layout();
-        assert!(ac.is(CORDER) && ac.is(CPREFER));
-        assert!(!ac.is(FORDER) && !ac.is(FPREFER));
-        assert!(!af.is(CORDER) && !af.is(CPREFER));
-        assert!(af.is(FORDER) && af.is(FPREFER));
-    }
-
-    #[test]
-    fn stride_layouts() {
-        let a = M::zeros((5, 5));
-
-        {
-            let v1 = a.slice(s![1.., ..]).layout();
-            let v2 = a.slice(s![.., 1..]).layout();
-
-            assert!(v1.is(CORDER) && v1.is(CPREFER));
-            assert!(!v1.is(FORDER) && !v1.is(FPREFER));
-            assert!(!v2.is(CORDER) && v2.is(CPREFER));
-            assert!(!v2.is(FORDER) && !v2.is(FPREFER));
-        }
-
-        let b = M::zeros((5, 5).f());
-
-        {
-            let v1 = b.slice(s![1.., ..]).layout();
-            let v2 = b.slice(s![.., 1..]).layout();
-
-            assert!(!v1.is(CORDER) && !v1.is(CPREFER));
-            assert!(!v1.is(FORDER) && v1.is(FPREFER));
-            assert!(!v2.is(CORDER) && !v2.is(CPREFER));
-            assert!(v2.is(FORDER) && v2.is(FPREFER));
-        }
-    }
-
-    #[test]
-    fn skip_layouts() {
-        let a = M::zeros((5, 5));
-        {
-            let v1 = a.slice(s![..;2, ..]).layout();
-            let v2 = a.slice(s![.., ..;2]).layout();
-
-            assert!(!v1.is(CORDER) && v1.is(CPREFER));
-            assert!(!v1.is(FORDER) && !v1.is(FPREFER));
-            assert!(!v2.is(CORDER) && !v2.is(CPREFER));
-            assert!(!v2.is(FORDER) && !v2.is(FPREFER));
-        }
-
-        let b = M::zeros((5, 5).f());
-        {
-            let v1 = b.slice(s![..;2, ..]).layout();
-            let v2 = b.slice(s![.., ..;2]).layout();
-
-            assert!(!v1.is(CORDER) && !v1.is(CPREFER));
-            assert!(!v1.is(FORDER) && !v1.is(FPREFER));
-            assert!(!v2.is(CORDER) && !v2.is(CPREFER));
-            assert!(!v2.is(FORDER) && v2.is(FPREFER));
-        }
-    }
-}
diff --git a/src/lib.rs b/src/lib.rs
index f65e82ec..ad7ffec7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11,6 +11,7 @@
     clippy::many_single_char_names,
     clippy::deref_addrof,
     clippy::unreadable_literal,
+    clippy::many_single_char_names
 )]
 
 //! The `ndarray` crate provides an *n*-dimensional container for general elements
@@ -52,9 +53,8 @@
 //! - Performance:
 //!   + Prefer higher order methods and arithmetic operations on arrays first,
 //!     then iteration, and as a last priority using indexed algorithms.
-//!   + The higher order functions like [`.map()`](ArrayBase::map),
-//!     [`.map_inplace()`](ArrayBase::map_inplace), [`.zip_mut_with()`](ArrayBase::zip_mut_with),
-//!     [`Zip`] and [`azip!()`](azip) are the most efficient ways
+//!   + The higher order functions like ``.map()``, ``.map_inplace()``,
+//!     ``.zip_mut_with()``, ``Zip`` and ``azip!()`` are the most efficient ways
 //!     to perform single traversal and lock step traversal respectively.
 //!   + Performance of an operation depends on the memory layout of the array
 //!     or array view. Especially if it's a binary operation, which
@@ -131,7 +131,9 @@ use crate::iterators::{ElementsBase, ElementsBaseMut, Iter, IterMut, Lanes, Lane
 
 pub use crate::arraytraits::AsArray;
 pub use crate::linalg_traits::{LinalgScalar, NdFloat};
-pub use crate::stacking::stack;
+
+#[allow(deprecated)]
+pub use crate::stacking::{concatenate, stack, stack_new_axis};
 
 pub use crate::impl_views::IndexLonger;
 pub use crate::shape_builder::ShapeBuilder;
@@ -176,11 +178,9 @@ mod linalg_traits;
 mod linspace;
 mod logspace;
 mod numeric_util;
-mod partial;
 mod shape_builder;
 #[macro_use]
 mod slice;
-mod split_at;
 mod stacking;
 #[macro_use]
 mod zip;
@@ -302,10 +302,9 @@ pub type Ixs = isize;
 /// Please see the documentation for the respective array view for an overview
 /// of methods specific to array views: [`ArrayView`], [`ArrayViewMut`].
 ///
-/// A view is created from an array using [`.view()`](ArrayBase::view),
-/// [`.view_mut()`](ArrayBase::view_mut), using
-/// slicing ([`.slice()`](ArrayBase::slice), [`.slice_mut()`](ArrayBase::slice_mut)) or from one of
-/// the many iterators that yield array views.
+/// A view is created from an array using `.view()`, `.view_mut()`, using
+/// slicing (`.slice()`, `.slice_mut()`) or from one of the many iterators
+/// that yield array views.
 ///
 /// You can also create an array view from a regular slice of data not
 /// allocated with `Array` — see array view methods or their `From` impls.
diff --git a/src/parallel/impl_par_methods.rs b/src/parallel/impl_par_methods.rs
index a4efa560..867cd07a 100644
--- a/src/parallel/impl_par_methods.rs
+++ b/src/parallel/impl_par_methods.rs
@@ -2,10 +2,6 @@ use crate::{Array, ArrayBase, DataMut, Dimension, IntoNdProducer, NdProducer, Zi
 use crate::AssignElem;
 
 use crate::parallel::prelude::*;
-use crate::parallel::par::ParallelSplits;
-use super::send_producer::SendProducer;
-
-use crate::partial::Partial;
 
 /// # Parallel methods
 ///
@@ -47,8 +43,6 @@ where
 
 // Zip
 
-const COLLECT_MAX_SPLITS: usize = 10;
-
 macro_rules! zip_impl {
     ($([$notlast:ident $($p:ident)*],)+) => {
         $(
@@ -77,46 +71,14 @@ macro_rules! zip_impl {
             /// inputs.
             ///
             /// If all inputs are c- or f-order respectively, that is preserved in the output.
-            pub fn par_apply_collect<R>(self, f: impl Fn($($p::Item,)* ) -> R + Sync + Send)
-                -> Array<R, D>
-                where R: Send
+            ///
+            /// Restricted to functions that produce copyable results for technical reasons; other
+            /// cases are not yet implemented.
+            pub fn par_apply_collect<R>(self, f: impl Fn($($p::Item,)* ) -> R + Sync + Send) -> Array<R, D>
+                where R: Copy + Send
             {
                 let mut output = self.uninitalized_for_current_layout::<R>();
-                let total_len = output.len();
-
-                // Create a parallel iterator that produces chunks of the zip with the output
-                // array.  It's crucial that both parts split in the same way, and in a way
-                // so that the chunks of the output are still contig.
-                //
-                // Use a raw view so that we can alias the output data here and in the partial
-                // result.
-                let splits = unsafe {
-                    ParallelSplits {
-                        iter: self.and(SendProducer::new(output.raw_view_mut().cast::<R>())),
-                        // Keep it from splitting the Zip down too small
-                        max_splits: COLLECT_MAX_SPLITS,
-                    }
-                };
-
-                let collect_result = splits.map(move |zip| {
-                    // Apply the mapping function on this chunk of the zip
-                    // Create a partial result for the contiguous slice of data being written to
-                    unsafe {
-                        zip.collect_with_partial(&f)
-                    }
-                })
-                .reduce(Partial::stub, Partial::try_merge);
-
-                if std::mem::needs_drop::<R>() {
-                    debug_assert_eq!(total_len, collect_result.len,
-                        "collect len is not correct, expected {}", total_len);
-                    assert!(collect_result.len == total_len,
-                        "Collect: Expected number of writes not completed");
-                }
-
-                // Here the collect result is complete, and we release its ownership and transfer
-                // it to the output array.
-                collect_result.release_ownership();
+                self.par_apply_assign_into(&mut output, f);
                 unsafe {
                     output.assume_init()
                 }
diff --git a/src/parallel/mod.rs b/src/parallel/mod.rs
index 12059cee..3583d342 100644
--- a/src/parallel/mod.rs
+++ b/src/parallel/mod.rs
@@ -10,11 +10,11 @@
 //! The following types implement parallel iterators, accessed using these
 //! methods:
 //!
-//! - [`Array`], [`ArcArray`] `.par_iter()` and `.par_iter_mut()`
-//! - [`ArrayView`] `.into_par_iter()`
-//! - [`ArrayViewMut`] `.into_par_iter()`
-//! - [`AxisIter`], [`AxisIterMut`] `.into_par_iter()`
-//! - [`AxisChunksIter`], [`AxisChunksIterMut`] `.into_par_iter()`
+//! - [`Array`], [`ArcArray`]: `.par_iter()` and `.par_iter_mut()`
+//! - [`ArrayView`](ArrayView): `.into_par_iter()`
+//! - [`ArrayViewMut`](ArrayViewMut): `.into_par_iter()`
+//! - [`AxisIter`](iter::AxisIter), [`AxisIterMut`](iter::AxisIterMut): `.into_par_iter()`
+//! - [`AxisChunksIter`](iter::AxisChunksIter), [`AxisChunksIterMut`](iter::AxisChunksIterMut): `.into_par_iter()`
 //! - [`Zip`] `.into_par_iter()`
 //!
 //! The following other parallelized methods exist:
@@ -39,6 +39,8 @@
 //! Compute the exponential of each element in an array, parallelized.
 //!
 //! ```
+//! extern crate ndarray;
+//!
 //! use ndarray::Array2;
 //! use ndarray::parallel::prelude::*;
 //!
@@ -59,6 +61,8 @@
 //! Use the parallel `.axis_iter()` to compute the sum of each row.
 //!
 //! ```
+//! extern crate ndarray;
+//!
 //! use ndarray::Array;
 //! use ndarray::Axis;
 //! use ndarray::parallel::prelude::*;
@@ -80,6 +84,8 @@
 //! Use the parallel `.axis_chunks_iter()` to process your data in chunks.
 //!
 //! ```
+//! extern crate ndarray;
+//!
 //! use ndarray::Array;
 //! use ndarray::Axis;
 //! use ndarray::parallel::prelude::*;
@@ -101,6 +107,8 @@
 //! Use zip for lock step function application across several arrays
 //!
 //! ```
+//! extern crate ndarray;
+//!
 //! use ndarray::Array3;
 //! use ndarray::Zip;
 //!
@@ -121,23 +129,6 @@
 //! }
 //! ```
 
-#[allow(unused_imports)] // used by rustdoc links
-use crate::{
-    ArrayBase,
-    Array,
-    ArcArray,
-    ArrayView,
-    ArrayViewMut,
-    Zip,
-};
-#[allow(unused_imports)] // used by rustdoc links
-use crate::iter::{
-    AxisIter,
-    AxisIterMut,
-    AxisChunksIter,
-    AxisChunksIterMut,
-};
-
 /// Into- traits for creating parallelized iterators and/or using [`par_azip!`]
 pub mod prelude {
     #[doc(no_inline)]
@@ -155,5 +146,4 @@ pub use crate::par_azip;
 mod impl_par_methods;
 mod into_impls;
 mod par;
-mod send_producer;
 mod zipmacro;
diff --git a/src/parallel/par.rs b/src/parallel/par.rs
index d9d592af..efd761ac 100644
--- a/src/parallel/par.rs
+++ b/src/parallel/par.rs
@@ -15,7 +15,6 @@ use crate::iter::AxisIter;
 use crate::iter::AxisIterMut;
 use crate::Dimension;
 use crate::{ArrayView, ArrayViewMut};
-use crate::split_at::SplitPreference;
 
 /// Parallel iterator wrapper.
 #[derive(Copy, Clone, Debug)]
@@ -171,14 +170,7 @@ macro_rules! par_iter_view_wrapper {
         fn fold_with<F>(self, folder: F) -> F
             where F: Folder<Self::Item>,
         {
-            Zip::from(self.0).fold_while(folder, |mut folder, elt| {
-                folder = folder.consume(elt);
-                if folder.full() {
-                    FoldWhile::Done(folder)
-                } else {
-                    FoldWhile::Continue(folder)
-                }
-            }).into_inner()
+            self.into_iter().fold(folder, move |f, elt| f.consume(elt))
         }
     }
 
@@ -251,7 +243,7 @@ macro_rules! zip_impl {
             type Item = ($($p::Item ,)*);
 
             fn split(self) -> (Self, Option<Self>) {
-                if !self.0.can_split() {
+                if self.0.size() <= 1 {
                     return (self, None)
                 }
                 let (a, b) = self.0.split();
@@ -283,53 +275,3 @@ zip_impl! {
     [P1 P2 P3 P4 P5],
     [P1 P2 P3 P4 P5 P6],
 }
-
-/// A parallel iterator (unindexed) that produces the splits of the array
-/// or producer `P`.
-pub(crate) struct ParallelSplits<P> {
-    pub(crate) iter: P,
-    pub(crate) max_splits: usize,
-}
-
-impl<P> ParallelIterator for ParallelSplits<P>
-    where P: SplitPreference + Send,
-{
-    type Item = P;
-
-    fn drive_unindexed<C>(self, consumer: C) -> C::Result
-        where C: UnindexedConsumer<Self::Item>
-    {
-        bridge_unindexed(self, consumer)
-    }
-
-    fn opt_len(&self) -> Option<usize> {
-        None
-    }
-}
-
-impl<P> UnindexedProducer for ParallelSplits<P>
-    where P: SplitPreference + Send,
-{
-    type Item = P;
-
-    fn split(self) -> (Self, Option<Self>) {
-        if self.max_splits == 0 || !self.iter.can_split() {
-            return (self, None)
-        }
-        let (a, b) = self.iter.split();
-        (ParallelSplits {
-            iter: a,
-            max_splits: self.max_splits - 1,
-        },
-        Some(ParallelSplits {
-            iter: b,
-            max_splits: self.max_splits - 1,
-        }))
-    }
-
-    fn fold_with<Fold>(self, folder: Fold) -> Fold
-        where Fold: Folder<Self::Item>,
-    {
-        folder.consume(self.iter)
-    }
-}
diff --git a/src/parallel/send_producer.rs b/src/parallel/send_producer.rs
deleted file mode 100644
index 5324b349..00000000
--- a/src/parallel/send_producer.rs
+++ /dev/null
@@ -1,83 +0,0 @@
-
-use crate::imp_prelude::*;
-use crate::{Layout, NdProducer};
-use std::ops::{Deref, DerefMut};
-
-/// An NdProducer that is unconditionally `Send`.
-#[repr(transparent)]
-pub(crate) struct SendProducer<T> {
-    inner: T
-}
-
-impl<T> SendProducer<T> {
-    /// Create an unconditionally `Send` ndproducer from the producer
-    pub(crate) unsafe fn new(producer: T) -> Self { Self { inner: producer } }
-}
-
-unsafe impl<P> Send for SendProducer<P> { }
-
-impl<P> Deref for SendProducer<P> {
-    type Target = P;
-    fn deref(&self) -> &P { &self.inner }
-}
-
-impl<P> DerefMut for SendProducer<P> {
-    fn deref_mut(&mut self) -> &mut P { &mut self.inner }
-}
-
-impl<P: NdProducer> NdProducer for SendProducer<P>
-    where P: NdProducer,
-{
-    type Item = P::Item;
-    type Dim = P::Dim;
-    type Ptr = P::Ptr;
-    type Stride = P::Stride;
-
-    private_impl! {}
-
-    #[inline(always)]
-    fn raw_dim(&self) -> Self::Dim {
-        self.inner.raw_dim()
-    }
-
-    #[inline(always)]
-    fn equal_dim(&self, dim: &Self::Dim) -> bool {
-        self.inner.equal_dim(dim)
-    }
-
-    #[inline(always)]
-    fn as_ptr(&self) -> Self::Ptr {
-        self.inner.as_ptr()
-    }
-
-    #[inline(always)]
-    fn layout(&self) -> Layout {
-        self.inner.layout()
-    }
-
-    #[inline(always)]
-    unsafe fn as_ref(&self, ptr: Self::Ptr) -> Self::Item {
-        self.inner.as_ref(ptr)
-    }
-
-    #[inline(always)]
-    unsafe fn uget_ptr(&self, i: &Self::Dim) -> Self::Ptr {
-        self.inner.uget_ptr(i)
-    }
-
-    #[inline(always)]
-    fn stride_of(&self, axis: Axis) -> Self::Stride {
-        self.inner.stride_of(axis)
-    }
-
-    #[inline(always)]
-    fn contiguous_stride(&self) -> Self::Stride {
-        self.inner.contiguous_stride()
-    }
-
-    fn split_at(self, axis: Axis, index: usize) -> (Self, Self) {
-        let (a, b) = self.inner.split_at(axis, index);
-        (Self { inner: a }, Self { inner: b })
-    }
-}
-
diff --git a/src/parallel/zipmacro.rs b/src/parallel/zipmacro.rs
index eadd36a8..99c3807f 100644
--- a/src/parallel/zipmacro.rs
+++ b/src/parallel/zipmacro.rs
@@ -34,6 +34,8 @@
 /// ## Examples
 ///
 /// ```rust
+/// extern crate ndarray;
+///
 /// use ndarray::Array2;
 /// use ndarray::parallel::par_azip;
 ///
diff --git a/src/partial.rs b/src/partial.rs
deleted file mode 100644
index 887e9382..00000000
--- a/src/partial.rs
+++ /dev/null
@@ -1,88 +0,0 @@
-// 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 std::ptr;
-
-/// Partial is a partially written contiguous slice of data;
-/// it is the owner of the elements, but not the allocation,
-/// and will drop the elements on drop.
-#[must_use]
-pub(crate) struct Partial<T> {
-    /// Data pointer
-    ptr: *mut T,
-    /// Current length
-    pub(crate) len: usize,
-}
-
-impl<T> Partial<T> {
-    /// Create an empty partial for this data pointer
-    ///
-    /// ## Safety
-    ///
-    /// Unless ownership is released, the Partial acts as an owner of the slice of data (not the
-    /// allocation); and will free the elements on drop; the pointer must be dereferenceable and
-    /// the `len` elements following it valid.
-    ///
-    /// The Partial has an accessible length field which must only be modified in trusted code.
-    pub(crate) unsafe fn new(ptr: *mut T) -> Self {
-        Self {
-            ptr,
-            len: 0,
-        }
-    }
-
-    #[cfg(feature = "rayon")]
-    pub(crate) fn stub() -> Self {
-        Self { len: 0, ptr: 0 as *mut _ }
-    }
-
-    #[cfg(feature = "rayon")]
-    pub(crate) fn is_stub(&self) -> bool {
-        self.ptr.is_null()
-    }
-
-    /// Release Partial's ownership of the written elements, and return the current length
-    pub(crate) fn release_ownership(mut self) -> usize {
-        let ret = self.len;
-        self.len = 0;
-        ret
-    }
-
-    #[cfg(feature = "rayon")]
-    /// Merge if they are in order (left to right) and contiguous.
-    /// Skips merge if T does not need drop.
-    pub(crate) fn try_merge(mut left: Self, right: Self) -> Self {
-        if !std::mem::needs_drop::<T>() {
-            return left;
-        }
-        // Merge the partial collect results; the final result will be a slice that
-        // covers the whole output.
-        if left.is_stub() {
-            right
-        } else if left.ptr.wrapping_add(left.len) == right.ptr {
-            left.len += right.release_ownership();
-            left
-        } else {
-            // failure to merge; this is a bug in collect, so we will never reach this
-            debug_assert!(false, "Partial: failure to merge left and right parts");
-            left
-        }
-    }
-}
-
-unsafe impl<T> Send for Partial<T> where T: Send { }
-
-impl<T> Drop for Partial<T> {
-    fn drop(&mut self) {
-        if !self.ptr.is_null() {
-            unsafe {
-                ptr::drop_in_place(std::slice::from_raw_parts_mut(self.ptr, self.len));
-            }
-        }
-    }
-}
diff --git a/src/slice.rs b/src/slice.rs
index 15765c61..58bff48b 100644
--- a/src/slice.rs
+++ b/src/slice.rs
@@ -496,6 +496,8 @@ impl_slicenextdim_larger!((), Slice);
 /// # Example
 ///
 /// ```
+/// extern crate ndarray;
+///
 /// use ndarray::{s, Array2, ArrayView2};
 ///
 /// fn laplacian(v: &ArrayView2<f32>) -> Array2<f32> {
@@ -526,6 +528,8 @@ impl_slicenextdim_larger!((), Slice);
 /// For example,
 ///
 /// ```
+/// # extern crate ndarray;
+/// #
 /// # use ndarray::prelude::*;
 /// #
 /// # fn main() {
diff --git a/src/split_at.rs b/src/split_at.rs
deleted file mode 100644
index b05e5834..00000000
--- a/src/split_at.rs
+++ /dev/null
@@ -1,48 +0,0 @@
-
-use crate::imp_prelude::*;
-
-/// Arrays and similar that can be split along an axis
-pub(crate) trait SplitAt  {
-    fn split_at(self, axis: Axis, index: usize) -> (Self, Self) where Self: Sized;
-}
-
-pub(crate) trait SplitPreference : SplitAt {
-    fn can_split(&self) -> bool;
-    fn split_preference(&self) -> (Axis, usize);
-    fn split(self) -> (Self, Self) where Self: Sized {
-        let (axis, index) = self.split_preference();
-        self.split_at(axis, index)
-    }
-}
-
-impl<D> SplitAt for D
-where
-    D: Dimension,
-{
-    fn split_at(self, axis: Axis, index: Ix) -> (Self, Self) {
-        let mut d1 = self;
-        let mut d2 = d1.clone();
-        let i = axis.index();
-        let len = d1[i];
-        d1[i] = index;
-        d2[i] = len - index;
-        (d1, d2)
-    }
-}
-
-impl<'a, A, D> SplitAt for ArrayViewMut<'a, A, D>
-    where D: Dimension
-{
-    fn split_at(self, axis: Axis, index: usize) -> (Self, Self) {
-        self.split_at(axis, index)
-    }
-}
-
-
-impl<A, D> SplitAt for RawArrayViewMut<A, D>
-    where D: Dimension
-{
-    fn split_at(self, axis: Axis, index: usize) -> (Self, Self) {
-        self.split_at(axis, index)
-    }
-}
diff --git a/src/stacking.rs b/src/stacking.rs
index 0f6161a3..e3158167 100644
--- a/src/stacking.rs
+++ b/src/stacking.rs
@@ -9,7 +9,7 @@
 use crate::error::{from_kind, ErrorKind, ShapeError};
 use crate::imp_prelude::*;
 
-/// Stack arrays along the given axis.
+/// Concatenate arrays along the given axis.
 ///
 /// ***Errors*** if the arrays have mismatching shapes, apart from along `axis`.
 /// (may be made more flexible in the future).<br>
@@ -29,10 +29,11 @@ use crate::imp_prelude::*;
 ///                  [3., 3.]]))
 /// );
 /// ```
-pub fn stack<'a, A, D>(
-    axis: Axis,
-    arrays: &[ArrayView<'a, A, D>],
-) -> Result<Array<A, D>, ShapeError>
+#[deprecated(
+    since = "0.13.2",
+    note = "Please use the `concatenate` function instead"
+)]
+pub fn stack<A, D>(axis: Axis, arrays: &[ArrayView<A, D>]) -> Result<Array<A, D>, ShapeError>
 where
     A: Copy,
     D: RemoveAxis,
@@ -76,7 +77,103 @@ where
     Ok(res)
 }
 
-/// Stack arrays along the given axis.
+/// Concatenate arrays along the given axis.
+///
+/// ***Errors*** if the arrays have mismatching shapes, apart from along `axis`.
+/// (may be made more flexible in the future).<br>
+/// ***Errors*** if `arrays` is empty, if `axis` is out of bounds,
+/// if the result is larger than is possible to represent.
+///
+/// ```
+/// use ndarray::{arr2, Axis, concatenate};
+///
+/// let a = arr2(&[[2., 2.],
+///                [3., 3.]]);
+/// assert!(
+///     concatenate(Axis(0), &[a.view(), a.view()])
+///     == Ok(arr2(&[[2., 2.],
+///                  [3., 3.],
+///                  [2., 2.],
+///                  [3., 3.]]))
+/// );
+/// ```
+#[allow(deprecated)]
+pub fn concatenate<A, D>(axis: Axis, arrays: &[ArrayView<A, D>]) -> Result<Array<A, D>, ShapeError>
+where
+    A: Copy,
+    D: RemoveAxis,
+{
+    stack(axis, arrays)
+}
+
+/// Stack arrays along the new axis.
+///
+/// ***Errors*** if the arrays have mismatching shapes.
+/// ***Errors*** if `arrays` is empty, if `axis` is out of bounds,
+/// if the result is larger than is possible to represent.
+///
+/// ```
+/// extern crate ndarray;
+///
+/// use ndarray::{arr2, arr3, stack_new_axis, Axis};
+///
+/// # fn main() {
+///
+/// let a = arr2(&[[2., 2.],
+///                [3., 3.]]);
+/// assert!(
+///     stack_new_axis(Axis(0), &[a.view(), a.view()])
+///     == Ok(arr3(&[[[2., 2.],
+///                   [3., 3.]],
+///                  [[2., 2.],
+///                   [3., 3.]]]))
+/// );
+/// # }
+/// ```
+pub fn stack_new_axis<A, D>(
+    axis: Axis,
+    arrays: &[ArrayView<A, D>],
+) -> Result<Array<A, D::Larger>, ShapeError>
+where
+    A: Copy,
+    D: Dimension,
+    D::Larger: RemoveAxis,
+{
+    if arrays.is_empty() {
+        return Err(from_kind(ErrorKind::Unsupported));
+    }
+    let common_dim = arrays[0].raw_dim();
+    // Avoid panic on `insert_axis` call, return an Err instead of it.
+    if axis.index() > common_dim.ndim() {
+        return Err(from_kind(ErrorKind::OutOfBounds));
+    }
+    let mut res_dim = common_dim.insert_axis(axis);
+
+    if arrays.iter().any(|a| a.raw_dim() != common_dim) {
+        return Err(from_kind(ErrorKind::IncompatibleShape));
+    }
+
+    res_dim.set_axis(axis, arrays.len());
+
+    // we can safely use uninitialized values here because they are Copy
+    // and we will only ever write to them
+    let size = res_dim.size();
+    let mut v = Vec::with_capacity(size);
+    unsafe {
+        v.set_len(size);
+    }
+    let mut res = Array::from_shape_vec(res_dim, v)?;
+
+    res.axis_iter_mut(axis)
+        .zip(arrays.into_iter())
+        .for_each(|(mut assign_view, array)| {
+            assign_view.assign(&array);
+        });
+
+    Ok(res)
+}
+
+/// Concatenate arrays along the given axis.
 ///
 /// Uses the [`stack`][1] function, calling `ArrayView::from(&a)` on each
 /// argument `a`.
@@ -86,6 +183,8 @@ where
 /// ***Panics*** if the `stack` function would return an error.
 ///
 /// ```
+/// extern crate ndarray;
+///
 /// use ndarray::{arr2, stack, Axis};
 ///
 /// # fn main() {
@@ -101,9 +200,81 @@ where
 /// );
 /// # }
 /// ```
+#[deprecated(
+    since = "0.13.2",
+    note = "Please use the `concatenate!` macro instead"
+)]
 #[macro_export]
 macro_rules! stack {
     ($axis:expr, $( $array:expr ),+ ) => {
         $crate::stack($axis, &[ $($crate::ArrayView::from(&$array) ),* ]).unwrap()
     }
 }
+
+/// Concatenate arrays along the given axis.
+///
+/// Uses the [`concatenate`][1] function, calling `ArrayView::from(&a)` on each
+/// argument `a`.
+///
+/// [1]: fn.concatenate.html
+///
+/// ***Panics*** if the `concatenate` function would return an error.
+///
+/// ```
+/// extern crate ndarray;
+///
+/// use ndarray::{arr2, concatenate, Axis};
+///
+/// # fn main() {
+///
+/// let a = arr2(&[[2., 2.],
+///                [3., 3.]]);
+/// assert!(
+///     concatenate![Axis(0), a, a]
+///     == arr2(&[[2., 2.],
+///               [3., 3.],
+///               [2., 2.],
+///               [3., 3.]])
+/// );
+/// # }
+/// ```
+#[macro_export]
+macro_rules! concatenate {
+    ($axis:expr, $( $array:expr ),+ ) => {
+        $crate::concatenate($axis, &[ $($crate::ArrayView::from(&$array) ),* ]).unwrap()
+    }
+}
+
+/// Stack arrays along the new axis.
+///
+/// Uses the [`stack_new_axis`][1] function, calling `ArrayView::from(&a)` on each
+/// argument `a`.
+///
+/// [1]: fn.stack_new_axis.html
+///
+/// ***Panics*** if the `stack` function would return an error.
+///
+/// ```
+/// extern crate ndarray;
+///
+/// use ndarray::{arr2, arr3, stack_new_axis, Axis};
+///
+/// # fn main() {
+///
+/// let a = arr2(&[[2., 2.],
+///                [3., 3.]]);
+/// assert!(
+///     stack_new_axis![Axis(0), a, a]
+///     == arr3(&[[[2., 2.],
+///                [3., 3.]],
+///               [[2., 2.],
+///                [3., 3.]]])
+/// );
+/// # }
+/// ```
+#[macro_export]
+macro_rules! stack_new_axis {
+    ($axis:expr, $( $array:expr ),+ ) => {
+        $crate::stack_new_axis($axis, &[ $($crate::ArrayView::from(&$array) ),* ]).unwrap()
+    }
+}
diff --git a/src/zip/mod.rs b/src/zip/mod.rs
index 333911c6..fc2812a4 100644
--- a/src/zip/mod.rs
+++ b/src/zip/mod.rs
@@ -16,11 +16,9 @@ use crate::AssignElem;
 use crate::IntoDimension;
 use crate::Layout;
 use crate::NdIndex;
-use crate::partial::Partial;
 
 use crate::indexes::{indices, Indices};
 use crate::layout::{CORDER, FORDER};
-use crate::split_at::{SplitPreference, SplitAt};
 
 /// Return if the expression is a break value.
 macro_rules! fold_while {
@@ -55,26 +53,17 @@ where
     D: Dimension,
 {
     pub(crate) fn layout_impl(&self) -> Layout {
-        let n = self.ndim();
-        if self.is_standard_layout() {
-            if n <= 1 {
-                Layout::one_dimensional()
+        Layout::new(if self.is_standard_layout() {
+            if self.ndim() <= 1 {
+                FORDER | CORDER
             } else {
-                Layout::c()
-            }
-        } else if n > 1 && self.raw_view().reversed_axes().is_standard_layout() {
-            Layout::f()
-        } else if n > 1 {
-            if self.stride_of(Axis(0)) == 1 {
-                Layout::fpref()
-            } else if self.stride_of(Axis(n - 1)) == 1 {
-                Layout::cpref()
-            } else {
-                Layout::none()
+                CORDER
             }
+        } else if self.ndim() > 1 && self.raw_view().reversed_axes().is_standard_layout() {
+            FORDER
         } else {
-            Layout::none()
-        }
+            0
+        })
     }
 }
 
@@ -91,6 +80,25 @@ where
     private_impl! {}
 }
 
+pub trait Splittable: Sized {
+    fn split_at(self, axis: Axis, index: Ix) -> (Self, Self);
+}
+
+impl<D> Splittable for D
+where
+    D: Dimension,
+{
+    fn split_at(self, axis: Axis, index: Ix) -> (Self, Self) {
+        let mut d1 = self;
+        let mut d2 = d1.clone();
+        let i = axis.index();
+        let len = d1[i];
+        d1[i] = index;
+        d2[i] = len - index;
+        (d1, d2)
+    }
+}
+
 /// Argument conversion into a producer.
 ///
 /// Slices and vectors can be used (equivalent to 1-dimensional array views).
@@ -178,7 +186,6 @@ pub trait NdProducer {
     fn split_at(self, axis: Axis, index: usize) -> (Self, Self)
     where
         Self: Sized;
-
     private_decl! {}
 }
 
@@ -580,9 +587,6 @@ pub struct Zip<Parts, D> {
     parts: Parts,
     dimension: D,
     layout: Layout,
-    /// The sum of the layout tendencies of the parts;
-    /// positive for c- and negative for f-layout preference.
-    layout_tendency: i32,
 }
 
 
@@ -601,12 +605,10 @@ where
     {
         let array = p.into_producer();
         let dim = array.raw_dim();
-        let layout = array.layout();
         Zip {
             dimension: dim,
-            layout,
+            layout: array.layout(),
             parts: (array,),
-            layout_tendency: layout.tendency(),
         }
     }
 }
@@ -659,29 +661,24 @@ where
         self.dimension[axis.index()]
     }
 
-    fn prefer_f(&self) -> bool {
-        !self.layout.is(CORDER) && (self.layout.is(FORDER) || self.layout_tendency < 0)
-    }
-
     /// Return an *approximation* to the max stride axis; if
     /// component arrays disagree, there may be no choice better than the
     /// others.
     fn max_stride_axis(&self) -> Axis {
-        let i = if self.prefer_f() {
-            self
+        let i = match self.layout.flag() {
+            FORDER => self
                 .dimension
                 .slice()
                 .iter()
                 .rposition(|&len| len > 1)
-                .unwrap_or(self.dimension.ndim() - 1)
-        } else {
+                .unwrap_or(self.dimension.ndim() - 1),
             /* corder or default */
-            self
+            _ => self
                 .dimension
                 .slice()
                 .iter()
                 .position(|&len| len > 1)
-                .unwrap_or(0)
+                .unwrap_or(0),
         };
         Axis(i)
     }
@@ -702,8 +699,7 @@ where
             self.apply_core_strided(acc, function)
         }
     }
-
-    fn apply_core_contiguous<F, Acc>(&mut self, acc: Acc, mut function: F) -> FoldWhile<Acc>
+    fn apply_core_contiguous<F, Acc>(&mut self, mut acc: Acc, mut function: F) -> FoldWhile<Acc>
     where
         F: FnMut(Acc, P::Item) -> FoldWhile<Acc>,
         P: ZippableTuple<Dim = D>,
@@ -712,36 +708,16 @@ where
         let size = self.dimension.size();
         let ptrs = self.parts.as_ptr();
         let inner_strides = self.parts.contiguous_stride();
-        unsafe {
-            self.inner(acc, ptrs, inner_strides, size, &mut function)
-        }
-    }
-
-    /// The innermost loop of the Zip apply methods
-    ///
-    /// Run the fold while operation on a stretch of elements with constant strides
-    ///
-    /// `ptr`: base pointer for the first element in this stretch
-    /// `strides`: strides for the elements in this stretch
-    /// `len`: number of elements
-    /// `function`: closure
-    unsafe fn inner<F, Acc>(&self, mut acc: Acc, ptr: P::Ptr, strides: P::Stride,
-                            len: usize, function: &mut F) -> FoldWhile<Acc>
-    where
-        F: FnMut(Acc, P::Item) -> FoldWhile<Acc>,
-        P: ZippableTuple
-    {
-        let mut i = 0;
-        while i < len {
-            let p = ptr.stride_offset(strides, i);
-            acc = fold_while!(function(acc, self.parts.as_ref(p)));
-            i += 1;
+        for i in 0..size {
+            unsafe {
+                let ptr_i = ptrs.stride_offset(inner_strides, i);
+                acc = fold_while![function(acc, self.parts.as_ref(ptr_i))];
+            }
         }
         FoldWhile::Continue(acc)
     }
 
-
-    fn apply_core_strided<F, Acc>(&mut self, acc: Acc, function: F) -> FoldWhile<Acc>
+    fn apply_core_strided<F, Acc>(&mut self, mut acc: Acc, mut function: F) -> FoldWhile<Acc>
     where
         F: FnMut(Acc, P::Item) -> FoldWhile<Acc>,
         P: ZippableTuple<Dim = D>,
@@ -750,67 +726,30 @@ where
         if n == 0 {
             panic!("Unreachable: ndim == 0 is contiguous")
         }
-        if n == 1 || self.layout_tendency >= 0 {
-            self.apply_core_strided_c(acc, function)
-        } else {
-            self.apply_core_strided_f(acc, function)
-        }
-    }
-
-    // Non-contiguous but preference for C - unroll over Axis(ndim - 1)
-    fn apply_core_strided_c<F, Acc>(&mut self, mut acc: Acc, mut function: F) -> FoldWhile<Acc>
-    where
-        F: FnMut(Acc, P::Item) -> FoldWhile<Acc>,
-        P: ZippableTuple<Dim = D>,
-    {
-        let n = self.dimension.ndim();
         let unroll_axis = n - 1;
         let inner_len = self.dimension[unroll_axis];
         self.dimension[unroll_axis] = 1;
         let mut index_ = self.dimension.first_index();
         let inner_strides = self.parts.stride_of(unroll_axis);
-        // Loop unrolled over closest axis
         while let Some(index) = index_ {
+            // Let's “unroll” the loop over the innermost axis
             unsafe {
                 let ptr = self.parts.uget_ptr(&index);
-                acc = fold_while![self.inner(acc, ptr, inner_strides, inner_len, &mut function)];
+                for i in 0..inner_len {
+                    let p = ptr.stride_offset(inner_strides, i);
+                    acc = fold_while!(function(acc, self.parts.as_ref(p)));
+                }
             }
 
             index_ = self.dimension.next_for(index);
         }
-        FoldWhile::Continue(acc)
-    }
-
-    // Non-contiguous but preference for F - unroll over Axis(0)
-    fn apply_core_strided_f<F, Acc>(&mut self, mut acc: Acc, mut function: F) -> FoldWhile<Acc>
-    where
-        F: FnMut(Acc, P::Item) -> FoldWhile<Acc>,
-        P: ZippableTuple<Dim = D>,
-    {
-        let unroll_axis = 0;
-        let inner_len = self.dimension[unroll_axis];
-        self.dimension[unroll_axis] = 1;
-        let index_ = self.dimension.first_index();
-        let inner_strides = self.parts.stride_of(unroll_axis);
-        // Loop unrolled over closest axis
-        if let Some(mut index) = index_ {
-            loop {
-                unsafe {
-                    let ptr = self.parts.uget_ptr(&index);
-                    acc = fold_while![self.inner(acc, ptr, inner_strides, inner_len, &mut function)];
-                }
-
-                if !self.dimension.next_for_f(&mut index) {
-                    break;
-                }
-            }
-        }
+        self.dimension[unroll_axis] = inner_len;
         FoldWhile::Continue(acc)
     }
 
     pub(crate) fn uninitalized_for_current_layout<T>(&self) -> Array<MaybeUninit<T>, D>
     {
-        let is_f = self.prefer_f();
+        let is_f = !self.layout.is(CORDER) && self.layout.is(FORDER);
         Array::maybe_uninit(self.dimension.clone().set_f(is_f))
     }
 }
@@ -1030,9 +969,15 @@ macro_rules! map_impl {
             pub fn and<P>(self, p: P) -> Zip<($($p,)* P::Output, ), D>
                 where P: IntoNdProducer<Dim=D>,
             {
-                let part = p.into_producer();
-                self.check(&part);
-                self.build_and(part)
+                let array = p.into_producer();
+                self.check(&array);
+                let part_layout = array.layout();
+                let ($($p,)*) = self.parts;
+                Zip {
+                    parts: ($($p,)* array, ),
+                    layout: self.layout.and(part_layout),
+                    dimension: self.dimension,
+                }
             }
 
             /// Include the producer `p` in the Zip, broadcasting if needed.
@@ -1045,20 +990,13 @@ macro_rules! map_impl {
                 where P: IntoNdProducer<Dim=D2, Output=ArrayView<'a, Elem, D2>, Item=&'a Elem>,
                       D2: Dimension,
             {
-                let part = p.into_producer().broadcast_unwrap(self.dimension.clone());
-                self.build_and(part)
-            }
-
-            fn build_and<P>(self, part: P) -> Zip<($($p,)* P, ), D>
-                where P: NdProducer<Dim=D>,
-            {
-                let part_layout = part.layout();
+                let array = p.into_producer().broadcast_unwrap(self.dimension.clone());
+                let part_layout = array.layout();
                 let ($($p,)*) = self.parts;
                 Zip {
-                    parts: ($($p,)* part, ),
-                    layout: self.layout.intersect(part_layout),
+                    parts: ($($p,)* array, ),
+                    layout: self.layout.and(part_layout),
                     dimension: self.dimension,
-                    layout_tendency: self.layout_tendency + part_layout.tendency(),
                 }
             }
 
@@ -1066,19 +1004,17 @@ macro_rules! map_impl {
             /// inputs.
             ///
             /// If all inputs are c- or f-order respectively, that is preserved in the output.
+            ///
+            /// Restricted to functions that produce copyable results for technical reasons; other
+            /// cases are not yet implemented.
             pub fn apply_collect<R>(self, f: impl FnMut($($p::Item,)* ) -> R) -> Array<R, D>
+                where R: Copy,
             {
-                // Make uninit result
+                // To support non-Copy elements, implementation of dropping partial array (on
+                // panic) is needed
                 let mut output = self.uninitalized_for_current_layout::<R>();
-
-                // Use partial to counts the number of filled elements, and can drop the right
-                // number of elements on unwinding (if it happens during apply/collect).
+                self.apply_assign_into(&mut output, f);
                 unsafe {
-                    let output_view = output.raw_view_mut().cast::<R>();
-                    self.and(output_view)
-                        .collect_with_partial(f)
-                        .release_ownership();
-
                     output.assume_init()
                 }
             }
@@ -1107,95 +1043,23 @@ macro_rules! map_impl {
             pub fn split(self) -> (Self, Self) {
                 debug_assert_ne!(self.size(), 0, "Attempt to split empty zip");
                 debug_assert_ne!(self.size(), 1, "Attempt to split zip with 1 elem");
-                SplitPreference::split(self)
-            }
-        }
-
-        expand_if!(@bool [$notlast]
-            // For collect; Last producer is a RawViewMut
-            #[allow(non_snake_case)]
-            impl<D, PLast, R, $($p),*> Zip<($($p,)* PLast), D>
-                where D: Dimension,
-                      $($p: NdProducer<Dim=D> ,)*
-                      PLast: NdProducer<Dim = D, Item = *mut R, Ptr = *mut R, Stride = isize>,
-            {
-                /// The inner workings of apply_collect and par_apply_collect
-                ///
-                /// Apply the function and collect the results into the output (last producer)
-                /// which should be a raw array view; a Partial that owns the written
-                /// elements is returned.
-                ///
-                /// Elements will be overwritten in place (in the sense of std::ptr::write).
-                ///
-                /// ## Safety
-                ///
-                /// The last producer is a RawArrayViewMut and must be safe to write into.
-                /// The producer must be c- or f-contig and have the same layout tendency
-                /// as the whole Zip.
-                ///
-                /// The returned Partial's proxy ownership of the elements must be handled,
-                /// before the array the raw view points to realizes its ownership.
-                pub(crate) unsafe fn collect_with_partial<F>(self, mut f: F) -> Partial<R>
-                    where F: FnMut($($p::Item,)* ) -> R
-                {
-                    // Get the last producer; and make a Partial that aliases its data pointer
-                    let (.., ref output) = &self.parts;
-                    debug_assert!(output.layout().is(CORDER | FORDER));
-                    debug_assert_eq!(output.layout().tendency() >= 0, self.layout_tendency >= 0);
-                    let mut partial = Partial::new(output.as_ptr());
-
-                    // Apply the mapping function on this zip
-                    // if we panic with unwinding; Partial will drop the written elements.
-                    let partial_len = &mut partial.len;
-                    self.apply(move |$($p,)* output_elem: *mut R| {
-                        output_elem.write(f($($p),*));
-                        if std::mem::needs_drop::<R>() {
-                            *partial_len += 1;
-                        }
-                    });
-
-                    partial
-                }
-            }
-        );
-
-        impl<D, $($p),*> SplitPreference for Zip<($($p,)*), D>
-            where D: Dimension,
-                  $($p: NdProducer<Dim=D> ,)*
-        {
-            fn can_split(&self) -> bool { self.size() > 1 }
-
-            fn split_preference(&self) -> (Axis, usize) {
                 // Always split in a way that preserves layout (if any)
                 let axis = self.max_stride_axis();
                 let index = self.len_of(axis) / 2;
-                (axis, index)
-            }
-        }
-
-        impl<D, $($p),*> SplitAt for Zip<($($p,)*), D>
-            where D: Dimension,
-                  $($p: NdProducer<Dim=D> ,)*
-        {
-            fn split_at(self, axis: Axis, index: usize) -> (Self, Self) {
                 let (p1, p2) = self.parts.split_at(axis, index);
                 let (d1, d2) = self.dimension.split_at(axis, index);
                 (Zip {
                     dimension: d1,
                     layout: self.layout,
                     parts: p1,
-                    layout_tendency: self.layout_tendency,
                 },
                 Zip {
                     dimension: d2,
                     layout: self.layout,
                     parts: p2,
-                    layout_tendency: self.layout_tendency,
                 })
             }
-
         }
-
         )+
     }
 }
diff --git a/src/zip/zipmacro.rs b/src/zip/zipmacro.rs
index ba2b3da2..3ac73b8f 100644
--- a/src/zip/zipmacro.rs
+++ b/src/zip/zipmacro.rs
@@ -38,6 +38,8 @@
 /// ## Examples
 ///
 /// ```rust
+/// extern crate ndarray;
+///
 /// use ndarray::{azip, Array1, Array2, Axis};
 ///
 /// type M = Array2<f32>;


Test patch:
diff --git a/tests/azip.rs b/tests/azip.rs
index 9027927f..90da4185 100644
--- a/tests/azip.rs
+++ b/tests/azip.rs
@@ -107,78 +107,6 @@ fn test_zip_assign_into_cell() {
     assert_abs_diff_eq!(a2, &b + &c, epsilon = 1e-6);
 }
 
-#[test]
-fn test_zip_collect_drop() {
-    use std::cell::RefCell;
-    use std::panic;
-
-    struct Recorddrop<'a>((usize, usize), &'a RefCell<Vec<(usize, usize)>>);
-
-    impl<'a> Drop for Recorddrop<'a> {
-        fn drop(&mut self) {
-            self.1.borrow_mut().push(self.0);
-        }
-    }
-
-    #[derive(Copy, Clone)]
-    enum Config {
-        CC,
-        CF,
-        FF,
-    }
-
-    impl Config {
-        fn a_is_f(self) -> bool {
-            match self {
-                Config::CC | Config::CF => false,
-                _ => true,
-            }
-        }
-        fn b_is_f(self) -> bool {
-            match self {
-                Config::CC => false,
-                _ => true,
-            }
-        }
-    }
-
-    let test_collect_panic = |config: Config, will_panic: bool, slice: bool| {
-        let mut inserts = RefCell::new(Vec::new());
-        let mut drops = RefCell::new(Vec::new());
-
-        let mut a = Array::from_shape_fn((5, 10).set_f(config.a_is_f()), |idx| idx);
-        let mut b = Array::from_shape_fn((5, 10).set_f(config.b_is_f()), |_| 0);
-        if slice {
-            a = a.slice_move(s![.., ..-1]);
-            b = b.slice_move(s![.., ..-1]);
-        }
-
-        let _result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
-            Zip::from(&a).and(&b).apply_collect(|&elt, _| {
-                if elt.0 > 3 && will_panic {
-                    panic!();
-                }
-                inserts.borrow_mut().push(elt);
-                Recorddrop(elt, &drops)
-            });
-        }));
-
-        println!("{:?}", inserts.get_mut());
-        println!("{:?}", drops.get_mut());
-
-        assert_eq!(inserts.get_mut().len(), drops.get_mut().len(), "Incorrect number of drops");
-        assert_eq!(inserts.get_mut(), drops.get_mut(), "Incorrect order of drops");
-    };
-
-    for &should_panic in &[true, false] {
-        for &should_slice in &[false, true] {
-            test_collect_panic(Config::CC, should_panic, should_slice);
-            test_collect_panic(Config::CF, should_panic, should_slice);
-            test_collect_panic(Config::FF, should_panic, should_slice);
-        }
-    }
-}
-
 
 #[test]
 fn test_azip_syntax_trailing_comma() {
diff --git a/tests/format.rs b/tests/format.rs
index 422fa295..5c2e2b6f 100644
--- a/tests/format.rs
+++ b/tests/format.rs
@@ -62,13 +62,13 @@ fn debug_format() {
         "\
 [[0, 0, 0, 0],
  [0, 0, 0, 0],
- [0, 0, 0, 0]], shape=[3, 4], strides=[4, 1], layout=Cc (0x5), const ndim=2"
+ [0, 0, 0, 0]], shape=[3, 4], strides=[4, 1], layout=C (0x1), const ndim=2"
     );
     assert_eq!(
         format!("{:?}", a.into_dyn()),
         "\
 [[0, 0, 0, 0],
  [0, 0, 0, 0],
- [0, 0, 0, 0]], shape=[3, 4], strides=[4, 1], layout=Cc (0x5), dynamic ndim=2"
+ [0, 0, 0, 0]], shape=[3, 4], strides=[4, 1], layout=C (0x1), dynamic ndim=2"
     );
 }
diff --git a/tests/par_zip.rs b/tests/par_zip.rs
index 36d7f43c..a24dc8e8 100644
--- a/tests/par_zip.rs
+++ b/tests/par_zip.rs
@@ -97,28 +97,6 @@ fn test_zip_collect() {
         assert_abs_diff_eq!(a, &b + &c, epsilon = 1e-6);
         assert_eq!(a.strides(), b.strides());
     }
-
-}
-
-#[test]
-#[cfg(feature = "approx")]
-fn test_zip_small_collect() {
-    use approx::assert_abs_diff_eq;
-
-    for m in 0..32 {
-        for n in 0..16 {
-            let dim = (m, n);
-            let b = Array::from_shape_fn(dim, |(i, j)| 1. / (i + 2 * j + 1) as f32);
-            let c = Array::from_shape_fn(dim, |(i, j)| f32::ln((1 + i + j) as f32));
-
-            {
-                let a = Zip::from(&b).and(&c).par_apply_collect(|x, y| x + y);
-
-                assert_abs_diff_eq!(a, &b + &c, epsilon = 1e-6);
-                assert_eq!(a.strides(), b.strides());
-            }
-        }
-    }
 }
 
 #[test]
diff --git a/tests/stacking.rs b/tests/stacking.rs
index a9a03171..94077def 100644
--- a/tests/stacking.rs
+++ b/tests/stacking.rs
@@ -1,7 +1,9 @@
-use ndarray::{arr2, aview1, stack, Array2, Axis, ErrorKind};
+#![allow(deprecated)]
+
+use ndarray::{arr2, arr3, aview1, concatenate, stack, Array2, Axis, ErrorKind, Ix1};
 
 #[test]
-fn stacking() {
+fn concatenating() {
     let a = arr2(&[[2., 2.], [3., 3.]]);
     let b = ndarray::stack(Axis(0), &[a.view(), a.view()]).unwrap();
     assert_eq!(b, arr2(&[[2., 2.], [3., 3.], [2., 2.], [3., 3.]]));
@@ -23,4 +25,43 @@ fn stacking() {
 
     let res: Result<Array2<f64>, _> = ndarray::stack(Axis(0), &[]);
     assert_eq!(res.unwrap_err().kind(), ErrorKind::Unsupported);
+
+    let a = arr2(&[[2., 2.], [3., 3.]]);
+    let b = ndarray::concatenate(Axis(0), &[a.view(), a.view()]).unwrap();
+    assert_eq!(b, arr2(&[[2., 2.], [3., 3.], [2., 2.], [3., 3.]]));
+
+    let c = concatenate![Axis(0), a, b];
+    assert_eq!(
+        c,
+        arr2(&[[2., 2.], [3., 3.], [2., 2.], [3., 3.], [2., 2.], [3., 3.]])
+    );
+
+    let d = concatenate![Axis(0), a.row(0), &[9., 9.]];
+    assert_eq!(d, aview1(&[2., 2., 9., 9.]));
+
+    let res = ndarray::concatenate(Axis(1), &[a.view(), c.view()]);
+    assert_eq!(res.unwrap_err().kind(), ErrorKind::IncompatibleShape);
+
+    let res = ndarray::concatenate(Axis(2), &[a.view(), c.view()]);
+    assert_eq!(res.unwrap_err().kind(), ErrorKind::OutOfBounds);
+
+    let res: Result<Array2<f64>, _> = ndarray::concatenate(Axis(0), &[]);
+    assert_eq!(res.unwrap_err().kind(), ErrorKind::Unsupported);
+}
+
+#[test]
+fn stacking() {
+    let a = arr2(&[[2., 2.], [3., 3.]]);
+    let b = ndarray::stack_new_axis(Axis(0), &[a.view(), a.view()]).unwrap();
+    assert_eq!(b, arr3(&[[[2., 2.], [3., 3.]], [[2., 2.], [3., 3.]]]));
+
+    let c = arr2(&[[3., 2., 3.], [2., 3., 2.]]);
+    let res = ndarray::stack_new_axis(Axis(1), &[a.view(), c.view()]);
+    assert_eq!(res.unwrap_err().kind(), ErrorKind::IncompatibleShape);
+
+    let res = ndarray::stack_new_axis(Axis(3), &[a.view(), a.view()]);
+    assert_eq!(res.unwrap_err().kind(), ErrorKind::OutOfBounds);
+
+    let res: Result<Array2<f64>, _> = ndarray::stack_new_axis::<_, Ix1>(Axis(0), &[]);
+    assert_eq!(res.unwrap_err().kind(), ErrorKind::Unsupported);
 }