diff --git a/src/data_traits.rs b/src/data_traits.rs index 3ea3125ce..b8fe84234 100644 --- a/src/data_traits.rs +++ b/src/data_traits.rs @@ -11,7 +11,9 @@ use std::mem::{self, size_of}; use std::sync::Arc; -use crate::{ArrayBase, Dimension, OwnedArcRepr, OwnedRcRepr, OwnedRepr, RawViewRepr, ViewRepr}; +use crate::{ + ArrayBase, CowRepr, Dimension, OwnedArcRepr, OwnedRcRepr, OwnedRepr, RawViewRepr, ViewRepr, +}; /// Array representation trait. /// @@ -423,3 +425,103 @@ unsafe impl DataOwned for OwnedArcRepr { self } } + +unsafe impl<'a, A> RawData for CowRepr<'a, A> { + type Elem = A; + fn _data_slice(&self) -> Option<&[A]> { + match self { + CowRepr::View(view) => view._data_slice(), + CowRepr::Owned(data) => data._data_slice(), + } + } + private_impl! {} +} + +unsafe impl<'a, A> RawDataMut for CowRepr<'a, A> +where + A: Clone, +{ + #[inline] + fn try_ensure_unique(array: &mut ArrayBase) + where + Self: Sized, + D: Dimension, + { + match array.data { + CowRepr::View(_) => { + let owned = array.to_owned(); + array.data = CowRepr::Owned(owned.data); + array.ptr = owned.ptr; + array.dim = owned.dim; + array.strides = owned.strides; + } + CowRepr::Owned(_) => {} + } + } + + #[inline] + fn try_is_unique(&mut self) -> Option { + Some(self.is_owned()) + } +} + +unsafe impl<'a, A> RawDataClone for CowRepr<'a, A> +where + A: Clone, +{ + unsafe fn clone_with_ptr(&self, ptr: *mut Self::Elem) -> (Self, *mut Self::Elem) { + match self { + CowRepr::View(view) => { + let (new_view, ptr) = view.clone_with_ptr(ptr); + (CowRepr::View(new_view), ptr) + } + CowRepr::Owned(data) => { + let (new_data, ptr) = data.clone_with_ptr(ptr); + (CowRepr::Owned(new_data), ptr) + } + } + } + + #[doc(hidden)] + unsafe fn clone_from_with_ptr( + &mut self, + other: &Self, + ptr: *mut Self::Elem, + ) -> *mut Self::Elem { + match (&mut *self, other) { + (CowRepr::View(self_), CowRepr::View(other)) => self_.clone_from_with_ptr(other, ptr), + (CowRepr::Owned(self_), CowRepr::Owned(other)) => self_.clone_from_with_ptr(other, ptr), + (_, CowRepr::Owned(other)) => { + let (cloned, ptr) = other.clone_with_ptr(ptr); + *self = CowRepr::Owned(cloned); + ptr + } + (_, CowRepr::View(other)) => { + let (cloned, ptr) = other.clone_with_ptr(ptr); + *self = CowRepr::View(cloned); + ptr + } + } + } +} + +unsafe impl<'a, A> Data for CowRepr<'a, A> { + #[inline] + fn into_owned(self_: ArrayBase, D>) -> ArrayBase, D> + where + A: Clone, + D: Dimension, + { + match self_.data { + CowRepr::View(_) => self_.to_owned(), + CowRepr::Owned(data) => ArrayBase { + data, + ptr: self_.ptr, + dim: self_.dim, + strides: self_.strides, + }, + } + } +} + +unsafe impl<'a, A> DataMut for CowRepr<'a, A> where A: Clone {} diff --git a/src/doc/ndarray_for_numpy_users/mod.rs b/src/doc/ndarray_for_numpy_users/mod.rs index 037752fa0..c2ec9e2d2 100644 --- a/src/doc/ndarray_for_numpy_users/mod.rs +++ b/src/doc/ndarray_for_numpy_users/mod.rs @@ -72,9 +72,11 @@ //! In `ndarray`, all arrays are instances of [`ArrayBase`][ArrayBase], but //! `ArrayBase` is generic over the ownership of the data. [`Array`][Array] //! owns its data; [`ArrayView`][ArrayView] is a view; -//! [`ArrayViewMut`][ArrayViewMut] is a mutable view; and -//! [`ArcArray`][ArcArray] has a reference-counted pointer to its data (with -//! copy-on-write mutation). Arrays and views follow Rust's aliasing rules. +//! [`ArrayViewMut`][ArrayViewMut] is a mutable view; [`CowArray`][CowArray] +//! either owns its data or is a view (with copy-on-write mutation of the view +//! variant); and [`ArcArray`][ArcArray] has a reference-counted pointer to its +//! data (with copy-on-write mutation). Arrays and views follow Rust's aliasing +//! rules. //! //! //! @@ -572,6 +574,7 @@ //! [.cols()]: ../../struct.ArrayBase.html#method.cols //! [.column()]: ../../struct.ArrayBase.html#method.column //! [.column_mut()]: ../../struct.ArrayBase.html#method.column_mut +//! [CowArray]: ../../type.CowArray.html //! [::default()]: ../../struct.ArrayBase.html#method.default //! [.diag()]: ../../struct.ArrayBase.html#method.diag //! [.dim()]: ../../struct.ArrayBase.html#method.dim diff --git a/src/impl_cow.rs b/src/impl_cow.rs new file mode 100644 index 000000000..52a06bfa2 --- /dev/null +++ b/src/impl_cow.rs @@ -0,0 +1,57 @@ +// Copyright 2019 ndarray developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::imp_prelude::*; + +/// Methods specific to `CowArray`. +/// +/// ***See also all methods for [`ArrayBase`]*** +/// +/// [`ArrayBase`]: struct.ArrayBase.html +impl<'a, A, D> CowArray<'a, A, D> +where + D: Dimension, +{ + /// Returns `true` iff the array is the view (borrowed) variant. + pub fn is_view(&self) -> bool { + self.data.is_view() + } + + /// Returns `true` iff the array is the owned variant. + pub fn is_owned(&self) -> bool { + self.data.is_owned() + } +} + +impl<'a, A, D> From> for CowArray<'a, A, D> +where + D: Dimension, +{ + fn from(view: ArrayView<'a, A, D>) -> CowArray<'a, A, D> { + ArrayBase { + data: CowRepr::View(view.data), + ptr: view.ptr, + dim: view.dim, + strides: view.strides, + } + } +} + +impl<'a, A, D> From> for CowArray<'a, A, D> +where + D: Dimension, +{ + fn from(array: Array) -> CowArray<'a, A, D> { + ArrayBase { + data: CowRepr::Owned(array.data), + ptr: array.ptr, + dim: array.dim, + strides: array.strides, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 4b6b9e615..9741989d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -193,7 +193,7 @@ mod imp_prelude { pub use crate::prelude::*; pub use crate::ArcArray; pub use crate::{ - Data, DataMut, DataOwned, DataShared, Ix, Ixs, RawData, RawDataMut, RawViewRepr, + CowRepr, Data, DataMut, DataOwned, DataShared, Ix, Ixs, RawData, RawDataMut, RawViewRepr, RemoveAxis, ViewRepr, }; } @@ -218,18 +218,21 @@ pub type Ixs = isize; /// The `ArrayBase` is parameterized by `S` for the data container and /// `D` for the dimensionality. /// -/// Type aliases [`Array`], [`ArcArray`], [`ArrayView`], and [`ArrayViewMut`] refer -/// to `ArrayBase` with different types for the data container. +/// Type aliases [`Array`], [`ArcArray`], [`CowArray`], [`ArrayView`], and +/// [`ArrayViewMut`] refer to `ArrayBase` with different types for the data +/// container. /// /// [`Array`]: type.Array.html /// [`ArcArray`]: type.ArcArray.html /// [`ArrayView`]: type.ArrayView.html /// [`ArrayViewMut`]: type.ArrayViewMut.html +/// [`CowArray`]: type.CowArray.html /// /// ## Contents /// /// + [Array](#array) /// + [ArcArray](#arcarray) +/// + [CowArray](#cowarray) /// + [Array Views](#array-views) /// + [Indexing and Dimension](#indexing-and-dimension) /// + [Loops, Producers and Iterators](#loops-producers-and-iterators) @@ -273,6 +276,16 @@ pub type Ixs = isize; /// [`view_mut()`](#method.view_mut) or [`get_mut()`](#method.get_mut), /// will break sharing and require a clone of the data (if it is not uniquely held). /// +/// ## `CowArray` +/// +/// [`CowArray`](type.CowArray.html) is analogous to +/// [`std::borrow::Cow`](https://doc.rust-lang.org/std/borrow/enum.Cow.html). +/// It can represent either an immutable view or a uniquely owned array. If a +/// `CowArray` instance is the immutable view variant, then calling a method +/// for mutating elements in the array will cause it to be converted into the +/// owned variant (by cloning all the elements) before the modification is +/// performed. +/// /// ## Array Views /// /// [`ArrayView`] and [`ArrayViewMut`] are read-only and read-write array views @@ -705,7 +718,7 @@ pub type Ixs = isize; /// /// /// -/// +/// /// /// /// @@ -721,6 +734,11 @@ pub type Ixs = isize; /// /// +/// @@ -751,6 +769,11 @@ pub type Ixs = isize; /// /// +/// @@ -781,6 +804,11 @@ pub type Ixs = isize; /// /// +/// @@ -791,6 +819,41 @@ pub type Ixs = isize; /// /// /// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// /// /// /// @@ -811,6 +874,11 @@ pub type Ixs = isize; /// /// +/// @@ -841,6 +909,11 @@ pub type Ixs = isize; /// /// +/// @@ -859,7 +932,7 @@ pub type Ixs = isize; /// equivalent with dim `D2` (e.g. converting from dynamic dim to const dim) /// /// -/// -/// -///
OutputInputInput
/// +/// `CowArray<'a, A, D>` +/// +/// +/// /// `ArrayView<'a, A, D>` /// /// /// +/// [`a.into_owned()`][.into_owned()] +/// +/// +/// /// [`a.to_owned()`][.to_owned()] /// /// /// +/// [`a.into_owned().into_shared()`][.into_shared()] +/// +/// +/// /// [`a.to_owned().into_shared()`][.into_shared()] /// ///
+/// +/// `CowArray<'a, A, D>` +/// +/// +/// +/// [`CowArray::from(a)`](type.CowArray.html#impl-From%2C%20D>>) +/// +/// +/// +/// [`CowArray::from(a.into_owned())`](type.CowArray.html#impl-From%2C%20D>>) +/// +/// +/// +/// no-op +/// +/// +/// +/// [`CowArray::from(a)`](type.CowArray.html#impl-From%2C%20D>>) +/// +/// +/// +/// [`CowArray::from(a.view())`](type.CowArray.html#impl-From%2C%20D>>) +/// +///
/// +/// [`a.view()`][.view()] +/// +/// +/// /// [`a.view()`][.view()] or [`a.reborrow()`][ArrayView::reborrow()] /// /// /// +/// [`a.view_mut()`][.view_mut()] +/// +/// +/// /// illegal /// /// +/// /// /// [`a.into_dimensionality::()`][.into_dimensionality()] /// @@ -874,7 +947,7 @@ pub type Ixs = isize; /// equivalent with dim `IxDyn` /// /// +/// /// /// [`a.into_dyn()`][.into_dyn()] /// @@ -889,7 +962,7 @@ pub type Ixs = isize; /// `Array` (new element type) /// /// +/// /// /// [`a.map(|x| x.do_your_conversion())`][.map()] /// @@ -1219,6 +1292,27 @@ pub type ArcArray = ArrayBase, D>; /// and so on. pub type Array = ArrayBase, D>; +/// An array with copy-on-write behavior. +/// +/// An `CowArray` represents either a uniquely owned array or a view of an +/// array. The `'a` corresponds to the lifetime of the view variant. +/// +/// This type is analogous to +/// [`std::borrow::Cow`](https://doc.rust-lang.org/std/borrow/enum.Cow.html). +/// If a `CowArray` instance is the immutable view variant, then calling a +/// method for mutating elements in the array will cause it to be converted +/// into the owned variant (by cloning all the elements) before the +/// modification is performed. +/// +/// Array views have all the methods of an array (see [`ArrayBase`][ab]). +/// +/// See also [`ArcArray`](type.ArcArray.html), which also provides +/// copy-on-write behavior but has a reference-counted pointer to the data +/// instead of either a view or a uniquely owned copy. +/// +/// [ab]: struct.ArrayBase.html +pub type CowArray<'a, A, D> = ArrayBase, D>; + /// A read-only array view. /// /// An array view represents an array or a part of it, created from @@ -1363,6 +1457,35 @@ impl ViewRepr { } } +/// CowArray's representation. +/// +/// *Don't use this type directly—use the type alias +/// [`CowArray`](type.CowArray.html) for the array type!* +pub enum CowRepr<'a, A> { + /// Borrowed data. + View(ViewRepr<&'a A>), + /// Owned data. + Owned(OwnedRepr), +} + +impl<'a, A> CowRepr<'a, A> { + /// Returns `true` iff the data is the `View` variant. + pub fn is_view(&self) -> bool { + match self { + CowRepr::View(_) => true, + CowRepr::Owned(_) => false, + } + } + + /// Returns `true` iff the data is the `Owned` variant. + pub fn is_owned(&self) -> bool { + match self { + CowRepr::View(_) => false, + CowRepr::Owned(_) => true, + } + } +} + mod impl_clone; mod impl_constructors; @@ -1488,6 +1611,9 @@ mod impl_views; // Array raw view methods mod impl_raw_views; +// Copy-on-write array methods +mod impl_cow; + /// A contiguous array shape of n dimensions. /// /// Either c- or f- memory ordered (*c* a.k.a *row major* is the default). diff --git a/src/prelude.rs b/src/prelude.rs index 8527f77c1..4d3887971 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -21,7 +21,8 @@ #[doc(no_inline)] #[allow(deprecated)] pub use crate::{ - ArcArray, Array, ArrayBase, ArrayView, ArrayViewMut, RawArrayView, RawArrayViewMut, RcArray, + ArcArray, Array, ArrayBase, ArrayView, ArrayViewMut, CowArray, RawArrayView, RawArrayViewMut, + RcArray, }; #[doc(no_inline)] diff --git a/tests/array.rs b/tests/array.rs index 75cd4edd0..000057b07 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -1969,3 +1969,127 @@ fn array_macros() { let empty2: Array = array![[]]; assert_eq!(empty2, array![[]]); } + +#[cfg(test)] +mod array_cow_tests { + use super::*; + + #[test] + fn test_is_variant() { + let arr: Array = array![[1, 2], [3, 4]]; + let arr_cow = CowArray::::from(arr.view()); + assert!(arr_cow.is_view()); + assert!(!arr_cow.is_owned()); + let arr_cow = CowArray::::from(arr); + assert!(arr_cow.is_owned()); + assert!(!arr_cow.is_view()); + } + + fn run_with_various_layouts(mut f: impl FnMut(Array2)) { + for all in vec![ + Array2::from_shape_vec((7, 8), (0..7 * 8).collect()).unwrap(), + Array2::from_shape_vec((7, 8).f(), (0..7 * 8).collect()).unwrap(), + ] { + f(all.clone()); + f(all.clone().slice_move(s![.., 2..5])); + f(all.clone().slice_move(s![3..5, 2..5])); + f(all.clone().slice_move(s![.., ..;2])); + f(all.clone().slice_move(s![..;3, ..])); + f(all.clone().slice_move(s![.., ..;-1])); + f(all.clone().slice_move(s![..;-2, ..;-1])); + f(all.clone().slice_move(s![2..5;-2, 3..6])); + f(all.clone().slice_move(s![2..5;-2, 3..6;-1])); + } + } + + #[test] + fn test_element_mutation() { + run_with_various_layouts(|arr: Array2| { + let mut expected = arr.clone(); + expected[(1, 1)] = 2; + + let mut arr_cow = CowArray::::from(arr.view()); + arr_cow[(1, 1)] = 2; + assert!(arr_cow.is_owned()); + assert_eq!(arr_cow, expected); + + let ptr = arr.as_ptr(); + let mut arr_cow = CowArray::::from(arr); + assert_eq!(arr_cow.as_ptr(), ptr); + arr_cow[(1, 1)] = 2; + assert_eq!(arr_cow.as_ptr(), ptr); + assert_eq!(arr_cow, expected); + }); + } + + #[test] + fn test_clone() { + run_with_various_layouts(|arr: Array2| { + let arr_cow = CowArray::::from(arr.view()); + let arr_cow_clone = arr_cow.clone(); + assert!(arr_cow_clone.is_view()); + assert_eq!(arr_cow, arr_cow_clone); + assert_eq!(arr_cow.dim(), arr_cow_clone.dim()); + assert_eq!(arr_cow.strides(), arr_cow_clone.strides()); + + let arr_cow = CowArray::::from(arr); + let arr_cow_clone = arr_cow.clone(); + assert!(arr_cow_clone.is_owned()); + assert_eq!(arr_cow, arr_cow_clone); + assert_eq!(arr_cow.dim(), arr_cow_clone.dim()); + assert_eq!(arr_cow.strides(), arr_cow_clone.strides()); + }); + } + + #[test] + fn test_clone_from() { + fn assert_eq_contents_and_layout(arr1: &CowArray, arr2: &CowArray) { + assert_eq!(arr1, arr2); + assert_eq!(arr1.dim(), arr2.dim()); + assert_eq!(arr1.strides(), arr2.strides()); + } + + run_with_various_layouts(|arr: Array2| { + run_with_various_layouts(|other_arr: Array2| { + let arr_cow_src = CowArray::::from(arr.view()); + let mut arr_cow_dst = CowArray::::from(other_arr.clone()); + arr_cow_dst.clone_from(&arr_cow_src); + assert!(arr_cow_dst.is_view()); + assert_eq_contents_and_layout(&arr_cow_src, &arr_cow_dst); + + let arr_cow_src = CowArray::::from(arr.view()); + let mut arr_cow_dst = CowArray::::from(other_arr.view()); + arr_cow_dst.clone_from(&arr_cow_src); + assert!(arr_cow_dst.is_view()); + assert_eq_contents_and_layout(&arr_cow_src, &arr_cow_dst); + + let arr_cow_src = CowArray::::from(arr.clone()); + let mut arr_cow_dst = CowArray::::from(other_arr.view()); + arr_cow_dst.clone_from(&arr_cow_src); + assert!(arr_cow_dst.is_owned()); + assert_eq_contents_and_layout(&arr_cow_src, &arr_cow_dst); + + let arr_cow_src = CowArray::::from(arr.clone()); + let mut arr_cow_dst = CowArray::::from(other_arr.clone()); + arr_cow_dst.clone_from(&arr_cow_src); + assert!(arr_cow_dst.is_owned()); + assert_eq_contents_and_layout(&arr_cow_src, &arr_cow_dst); + }); + }); + } + + #[test] + fn test_into_owned() { + run_with_various_layouts(|arr: Array2| { + let before = CowArray::::from(arr.view()); + let after = before.into_owned(); + assert_eq!(arr, after); + + let before = CowArray::::from(arr.clone()); + let ptr = before.as_ptr(); + let after = before.into_owned(); + assert_eq!(after.as_ptr(), ptr); + assert_eq!(arr, after); + }); + } +}