Skip to content

Commit 3156346

Browse files
committed
Adds some aliases to avoid breaking changes, and adds an example of how to write functionality with the new types
1 parent fb098d3 commit 3156346

15 files changed

+513
-136
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ target/
44

55
# Editor settings
66
.vscode
7+
8+
# Apple details
9+
**/.DS_Store

examples/functions_and_traits.rs

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
//! Examples of how to write functions and traits that operate on `ndarray` types.
2+
//!
3+
//! `ndarray` has four kinds of array types that users may interact with:
4+
//! 1. [`ArrayBase`], the owner of the layout that describes an array in memory;
5+
//! this includes [`ndarray::Array`], [`ndarray::ArcArray`], [`ndarray::ArrayView`],
6+
//! [`ndarray::RawArrayView`], and other variants.
7+
//! 2. [`ArrayRef`], which represents a read-safe, uniquely-owned look at an array.
8+
//! 3. [`RawRef`], which represents a read-unsafe, possibly-shared look at an array.
9+
//! 4. [`LayoutRef`], which represents a look at an array's underlying structure,
10+
//! but does not allow data reading of any kind.
11+
//!
12+
//! Below, we illustrate how to write functions and traits for most variants of these types.
13+
14+
use ndarray::{ArrayBase, ArrayRef, Data, DataMut, Dimension, LayoutRef, RawData, RawDataMut, RawRef};
15+
16+
/// Take an array with the most basic requirements.
17+
///
18+
/// This function takes its data as owning. It is very rare that a user will need to specifically
19+
/// take a reference to an `ArrayBase`, rather than to one of the other four types.
20+
#[rustfmt::skip]
21+
fn takes_base_raw<S: RawData, D>(arr: ArrayBase<S, D>) -> ArrayBase<S, D>
22+
{
23+
// These skip from a possibly-raw array to `RawRef` and `LayoutRef`, and so must go through `AsRef`
24+
takes_rawref(arr.as_ref()); // Caller uses `.as_ref`
25+
takes_rawref_asref(&arr); // Implementor uses `.as_ref`
26+
takes_layout(arr.as_ref()); // Caller uses `.as_ref`
27+
takes_layout_asref(&arr); // Implementor uses `.as_ref`
28+
29+
arr
30+
}
31+
32+
/// Similar to above, but allow us to read the underlying data.
33+
#[rustfmt::skip]
34+
fn takes_base_raw_mut<S: RawDataMut, D>(mut arr: ArrayBase<S, D>) -> ArrayBase<S, D>
35+
{
36+
// These skip from a possibly-raw array to `RawRef` and `LayoutRef`, and so must go through `AsMut`
37+
takes_rawref_mut(arr.as_mut()); // Caller uses `.as_mut`
38+
takes_rawref_asmut(&mut arr); // Implementor uses `.as_mut`
39+
takes_layout_mut(arr.as_mut()); // Caller uses `.as_mut`
40+
takes_layout_asmut(&mut arr); // Implementor uses `.as_mut`
41+
42+
arr
43+
}
44+
45+
/// Now take an array whose data is safe to read.
46+
#[allow(dead_code)]
47+
fn takes_base<S: Data, D>(mut arr: ArrayBase<S, D>) -> ArrayBase<S, D>
48+
{
49+
// Raw call
50+
arr = takes_base_raw(arr);
51+
52+
// No need for AsRef, since data is safe
53+
takes_arrref(&arr);
54+
takes_rawref(&arr);
55+
takes_rawref_asref(&arr);
56+
takes_layout(&arr);
57+
takes_layout_asref(&arr);
58+
59+
arr
60+
}
61+
62+
/// Now, an array whose data is safe to read and that we can mutate.
63+
///
64+
/// Notice that we include now a trait bound on `D: Dimension`; this is necessary in order
65+
/// for the `ArrayBase` to dereference to an `ArrayRef` (or to any of the other types).
66+
#[allow(dead_code)]
67+
fn takes_base_mut<S: DataMut, D: Dimension>(mut arr: ArrayBase<S, D>) -> ArrayBase<S, D>
68+
{
69+
// Raw call
70+
arr = takes_base_raw_mut(arr);
71+
72+
// No need for AsMut, since data is safe
73+
takes_arrref_mut(&mut arr);
74+
takes_rawref_mut(&mut arr);
75+
takes_rawref_asmut(&mut arr);
76+
takes_layout_mut(&mut arr);
77+
takes_layout_asmut(&mut arr);
78+
79+
arr
80+
}
81+
82+
/// Now for new stuff: we want to read (but not alter) any array whose data is safe to read.
83+
///
84+
/// This is probably the most common functionality that one would want to write.
85+
/// As we'll see below, calling this function is very simple for `ArrayBase<S: Data, D>`.
86+
fn takes_arrref<A, D>(arr: &ArrayRef<A, D>)
87+
{
88+
// No need for AsRef, since data is safe
89+
takes_rawref(arr);
90+
takes_rawref_asref(arr);
91+
takes_layout(arr);
92+
takes_layout_asref(arr);
93+
}
94+
95+
/// Now we want any array whose data is safe to mutate.
96+
///
97+
/// **Importantly**, any array passed to this function is guaranteed to uniquely point to its data.
98+
/// As a result, passing a shared array to this function will **silently** un-share the array.
99+
#[allow(dead_code)]
100+
fn takes_arrref_mut<A, D>(arr: &mut ArrayRef<A, D>)
101+
{
102+
// Immutable call
103+
takes_arrref(arr);
104+
105+
// No need for AsMut, since data is safe
106+
takes_rawref_mut(arr);
107+
takes_rawref_asmut(arr);
108+
takes_layout_mut(arr);
109+
takes_rawref_asmut(arr);
110+
}
111+
112+
/// Now, we no longer care about whether we can safely read data.
113+
///
114+
/// This is probably the rarest type to deal with, since `LayoutRef` can access and modify an array's
115+
/// shape and strides, and even do in-place slicing. As a result, `RawRef` is only for functionality
116+
/// that requires unsafe data access, something that `LayoutRef` can't do.
117+
///
118+
/// Writing functions and traits that deal with `RawRef`s and `LayoutRef`s can be done two ways:
119+
/// 1. Directly on the types; calling these functions on arrays whose data are not known to be safe
120+
/// to dereference (i.e., raw array views or `ArrayBase<S: RawData, D>`) must explicitly call `.as_ref()`.
121+
/// 2. Via a generic with `: AsRef<RawRef<A, D>>`; doing this will allow direct calling for all `ArrayBase` and
122+
/// `ArrayRef` instances.
123+
/// We'll demonstrate #1 here for both immutable and mutable references, then #2 directly below.
124+
#[allow(dead_code)]
125+
fn takes_rawref<A, D>(arr: &RawRef<A, D>)
126+
{
127+
takes_layout(arr);
128+
takes_layout_asref(arr);
129+
}
130+
131+
/// Mutable, directly take `RawRef`
132+
#[allow(dead_code)]
133+
fn takes_rawref_mut<A, D>(arr: &mut RawRef<A, D>)
134+
{
135+
takes_layout(arr);
136+
takes_layout_asmut(arr);
137+
}
138+
139+
/// Immutable, take a generic that implements `AsRef` to `RawRef`
140+
#[allow(dead_code)]
141+
fn takes_rawref_asref<T, A, D>(_arr: &T)
142+
where T: AsRef<RawRef<A, D>>
143+
{
144+
takes_layout(_arr.as_ref());
145+
takes_layout_asref(_arr.as_ref());
146+
}
147+
148+
/// Mutable, take a generic that implements `AsMut` to `RawRef`
149+
#[allow(dead_code)]
150+
fn takes_rawref_asmut<T, A, D>(_arr: &mut T)
151+
where T: AsMut<RawRef<A, D>>
152+
{
153+
takes_layout_mut(_arr.as_mut());
154+
takes_layout_asmut(_arr.as_mut());
155+
}
156+
157+
/// Finally, there's `LayoutRef`: this type provides read and write access to an array's *structure*, but not its *data*.
158+
///
159+
/// Practically, this means that functions that only read/modify an array's shape or strides,
160+
/// such as checking dimensionality or slicing, should take `LayoutRef`.
161+
///
162+
/// Like `RawRef`, functions can be written either directly on `LayoutRef` or as generics with `: AsRef<LayoutRef<A, D>>>`.
163+
#[allow(dead_code)]
164+
fn takes_layout<A, D>(_arr: &LayoutRef<A, D>) {}
165+
166+
/// Mutable, directly take `LayoutRef`
167+
#[allow(dead_code)]
168+
fn takes_layout_mut<A, D>(_arr: &mut LayoutRef<A, D>) {}
169+
170+
/// Immutable, take a generic that implements `AsRef` to `LayoutRef`
171+
#[allow(dead_code)]
172+
fn takes_layout_asref<T: AsRef<LayoutRef<A, D>>, A, D>(_arr: &T) {}
173+
174+
/// Mutable, take a generic that implements `AsMut` to `LayoutRef`
175+
#[allow(dead_code)]
176+
fn takes_layout_asmut<T: AsMut<LayoutRef<A, D>>, A, D>(_arr: &mut T) {}
177+
178+
fn main() {}

src/alias_slicing.rs renamed to src/alias_asref.rs

+137-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1-
use crate::{iter::Axes, ArrayBase, Axis, AxisDescription, Dimension, LayoutRef, RawData, Slice, SliceArg};
1+
use crate::{
2+
iter::Axes,
3+
ArrayBase,
4+
Axis,
5+
AxisDescription,
6+
Dimension,
7+
LayoutRef,
8+
RawArrayView,
9+
RawData,
10+
RawRef,
11+
Slice,
12+
SliceArg,
13+
};
214

315
impl<S: RawData, D: Dimension> ArrayBase<S, D>
416
{
@@ -69,19 +81,19 @@ impl<S: RawData, D: Dimension> ArrayBase<S, D>
6981
/// contiguous in memory, it has custom strides, etc.
7082
pub fn is_standard_layout(&self) -> bool
7183
{
72-
self.as_ref().is_standard_layout()
84+
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).is_standard_layout()
7385
}
7486

7587
/// Return true if the array is known to be contiguous.
7688
pub(crate) fn is_contiguous(&self) -> bool
7789
{
78-
self.as_ref().is_contiguous()
90+
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).is_contiguous()
7991
}
8092

8193
/// Return an iterator over the length and stride of each axis.
8294
pub fn axes(&self) -> Axes<'_, D>
8395
{
84-
self.as_ref().axes()
96+
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).axes()
8597
}
8698

8799
/*
@@ -170,9 +182,129 @@ impl<S: RawData, D: Dimension> ArrayBase<S, D>
170182
self.as_mut().merge_axes(take, into)
171183
}
172184

185+
/// Return a raw view of the array.
186+
#[inline]
187+
pub fn raw_view(&self) -> RawArrayView<S::Elem, D>
188+
{
189+
<Self as AsRef<RawRef<_, _>>>::as_ref(self).raw_view()
190+
}
191+
192+
/// Return a pointer to the first element in the array.
193+
///
194+
/// Raw access to array elements needs to follow the strided indexing
195+
/// scheme: an element at multi-index *I* in an array with strides *S* is
196+
/// located at offset
197+
///
198+
/// *Σ<sub>0 ≤ k < d</sub> I<sub>k</sub> × S<sub>k</sub>*
199+
///
200+
/// where *d* is `self.ndim()`.
201+
#[inline(always)]
202+
pub fn as_ptr(&self) -> *const S::Elem
203+
{
204+
<Self as AsRef<RawRef<_, _>>>::as_ref(self).as_ptr()
205+
}
206+
207+
/// Return the total number of elements in the array.
208+
pub fn len(&self) -> usize
209+
{
210+
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).len()
211+
}
212+
213+
/// Return the length of `axis`.
214+
///
215+
/// The axis should be in the range `Axis(` 0 .. *n* `)` where *n* is the
216+
/// number of dimensions (axes) of the array.
217+
///
218+
/// ***Panics*** if the axis is out of bounds.
219+
#[track_caller]
220+
pub fn len_of(&self, axis: Axis) -> usize
221+
{
222+
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).len_of(axis)
223+
}
224+
225+
/// Return whether the array has any elements
226+
pub fn is_empty(&self) -> bool
227+
{
228+
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).is_empty()
229+
}
230+
231+
/// Return the number of dimensions (axes) in the array
232+
pub fn ndim(&self) -> usize
233+
{
234+
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).ndim()
235+
}
236+
237+
/// Return the shape of the array in its “pattern” form,
238+
/// an integer in the one-dimensional case, tuple in the n-dimensional cases
239+
/// and so on.
240+
pub fn dim(&self) -> D::Pattern
241+
{
242+
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).dim()
243+
}
244+
245+
/// Return the shape of the array as it's stored in the array.
246+
///
247+
/// This is primarily useful for passing to other `ArrayBase`
248+
/// functions, such as when creating another array of the same
249+
/// shape and dimensionality.
250+
///
251+
/// ```
252+
/// use ndarray::Array;
253+
///
254+
/// let a = Array::from_elem((2, 3), 5.);
255+
///
256+
/// // Create an array of zeros that's the same shape and dimensionality as `a`.
257+
/// let b = Array::<f64, _>::zeros(a.raw_dim());
258+
/// ```
259+
pub fn raw_dim(&self) -> D
260+
{
261+
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).raw_dim()
262+
}
263+
264+
/// Return the shape of the array as a slice.
265+
///
266+
/// Note that you probably don't want to use this to create an array of the
267+
/// same shape as another array because creating an array with e.g.
268+
/// [`Array::zeros()`](ArrayBase::zeros) using a shape of type `&[usize]`
269+
/// results in a dynamic-dimensional array. If you want to create an array
270+
/// that has the same shape and dimensionality as another array, use
271+
/// [`.raw_dim()`](ArrayBase::raw_dim) instead:
272+
///
273+
/// ```rust
274+
/// use ndarray::{Array, Array2};
275+
///
276+
/// let a = Array2::<i32>::zeros((3, 4));
277+
/// let shape = a.shape();
278+
/// assert_eq!(shape, &[3, 4]);
279+
///
280+
/// // Since `a.shape()` returned `&[usize]`, we get an `ArrayD` instance:
281+
/// let b = Array::zeros(shape);
282+
/// assert_eq!(a.clone().into_dyn(), b);
283+
///
284+
/// // To get the same dimension type, use `.raw_dim()` instead:
285+
/// let c = Array::zeros(a.raw_dim());
286+
/// assert_eq!(a, c);
287+
/// ```
288+
pub fn shape(&self) -> &[usize]
289+
{
290+
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).shape()
291+
}
292+
173293
/// Return the strides of the array as a slice.
174294
pub fn strides(&self) -> &[isize]
175295
{
176-
self.as_ref().strides()
296+
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).strides()
297+
}
298+
299+
/// Return the stride of `axis`.
300+
///
301+
/// The axis should be in the range `Axis(` 0 .. *n* `)` where *n* is the
302+
/// number of dimensions (axes) of the array.
303+
///
304+
/// ***Panics*** if the axis is out of bounds.
305+
#[track_caller]
306+
pub fn stride_of(&self, axis: Axis) -> isize
307+
{
308+
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).stride_of(axis)
177309
}
178310
}

src/impl_2d.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ impl<A> LayoutRef<A, Ix2>
6565
/// ```
6666
pub fn nrows(&self) -> usize
6767
{
68-
self.as_ref().len_of(Axis(0))
68+
self.len_of(Axis(0))
6969
}
7070
}
7171

@@ -124,7 +124,7 @@ impl<A> LayoutRef<A, Ix2>
124124
/// ```
125125
pub fn ncols(&self) -> usize
126126
{
127-
self.as_ref().len_of(Axis(1))
127+
self.len_of(Axis(1))
128128
}
129129

130130
/// Return true if the array is square, false otherwise.
@@ -144,7 +144,7 @@ impl<A> LayoutRef<A, Ix2>
144144
/// ```
145145
pub fn is_square(&self) -> bool
146146
{
147-
let (m, n) = self.as_ref().dim();
147+
let (m, n) = self.dim();
148148
m == n
149149
}
150150
}

0 commit comments

Comments
 (0)