Skip to content

Commit 7c56e84

Browse files
authored
Update the docs and examples for the new Sized implementation (#1535)
1 parent a3b3537 commit 7c56e84

File tree

2 files changed

+126
-107
lines changed

2 files changed

+126
-107
lines changed

examples/functions_and_traits.rs

Lines changed: 123 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -7,136 +7,182 @@
77
//! 2. [`ArrayRef`], which represents a read-safe, uniquely-owned look at an array.
88
//! 3. [`RawRef`], which represents a read-unsafe, possibly-shared look at an array.
99
//! 4. [`LayoutRef`], which represents a look at an array's underlying structure,
10-
//! but does not allow data reading of any kind.
10+
//! but does not allow reading data of any kind.
1111
//!
1212
//! Below, we illustrate how to write functions and traits for most variants of these types.
1313
14-
use ndarray::{ArrayBase, ArrayRef, Data, DataMut, Dimension, LayoutRef, RawData, RawDataMut, RawRef};
14+
use ndarray::{ArrayBase, ArrayRef, Data, DataMut, Dimension, LayoutRef, RawRef};
1515

16-
/// Take an array with the most basic requirements.
16+
/// First, the newest pattern: this function accepts arrays whose data are safe to
17+
/// dereference and uniquely held.
1718
///
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>
19+
/// This is probably the most common pattern for users.
20+
/// Once we have an array reference, we can go to [`RawRef`] and [`LayoutRef`] very easily.
21+
fn takes_arrref<A, D>(arr: &ArrayRef<A, D>)
2222
{
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
23+
// Since `ArrayRef` implements `Deref` to `RawRef`, we can pass `arr` directly to a function
24+
// that takes `RawRef`. Similarly, since `RawRef` implements `Deref` to `LayoutRef`, we can pass
25+
// `arr` directly to a function that takes `LayoutRef`.
26+
takes_rawref(arr); // &ArrayRef -> &RawRef
27+
takes_layout(arr); // &ArrayRef -> &RawRef -> &LayoutRef
28+
29+
// We can also pass `arr` to functions that accept `RawRef` and `LayoutRef` via `AsRef`.
30+
// These alternative function signatures are important for other types, but we see that when
31+
// we have an `ArrayRef`, we can call them very simply.
32+
takes_rawref_asref(arr); // &ArrayRef -> &RawRef
33+
takes_layout_asref(arr); // &ArrayRef -> &LayoutRef
3034
}
3135

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>
36+
/// Now we want any array whose data is safe to mutate.
37+
///
38+
/// Importantly, any array passed to this function is guaranteed to uniquely point to its data.
39+
/// As a result, passing a shared array to this function will silently un-share the array.
40+
/// So, ***users should only accept `&mut ArrayRef` when they want to mutate data***.
41+
/// If they just want to mutate shape and strides, use `&mut LayoutRef` or `&AsMut<LayoutRef>`.
42+
#[allow(dead_code)]
43+
fn takes_arrref_mut<A, D>(arr: &mut ArrayRef<A, D>)
3544
{
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`
45+
// We can do everything we did with a `&ArrayRef`
46+
takes_arrref(arr);
4147

42-
arr
48+
// Similarly, we can pass this to functions that accept mutable references
49+
// to our other array reference types. These first two happen via `Deref`...
50+
takes_rawref_mut(arr);
51+
takes_layout_mut(arr);
52+
53+
// ... and these two happen via `AsRef`.
54+
takes_rawref_asmut(arr);
55+
takes_rawref_asmut(arr);
4356
}
4457

45-
/// Now take an array whose data is safe to read.
58+
/// Now let's go back and look at the way to write functions prior to 0.17: using `ArrayBase`.
59+
///
60+
/// This function signature says three things:
61+
/// 1. Let me take a read only reference (that's the `&`)
62+
/// 2. Of an array whose data is safe to dereference (that's the `S: Data`)
63+
/// 3. And whose data is read-only (also `S: Data`)
64+
///
65+
/// Let's see what we can do with this array:
4666
#[allow(dead_code)]
47-
fn takes_base<S: Data, D>(mut arr: ArrayBase<S, D>) -> ArrayBase<S, D>
67+
fn takes_base<S: Data, D>(arr: &ArrayBase<S, D>)
4868
{
49-
// Raw call
50-
arr = takes_base_raw(arr);
69+
// First off: we can pass it to functions that accept `&ArrayRef`.
70+
//
71+
// This is always "cheap", in the sense that even if `arr` is an
72+
// `ArcArray` that shares its data, using this call will not un-share that data.
73+
takes_arrref(arr);
5174

52-
// No need for AsRef, since data is safe
53-
takes_arrref(&arr);
75+
// We can also pass it to functions that accept `RawRef` and `LayoutRef`
76+
// in the usual two ways:
5477
takes_rawref(&arr);
55-
takes_rawref_asref(&arr);
5678
takes_layout(&arr);
79+
//
80+
takes_rawref_asref(&arr);
5781
takes_layout_asref(&arr);
82+
}
5883

59-
arr
84+
/// Now, let's take a mutable reference to an `ArrayBase` - but let's keep `S: Data`, such
85+
/// that we are allowed to change the _layout_ of the array, but not its data.
86+
fn takes_base_mut<S: Data, D>(arr: &mut ArrayBase<S, D>)
87+
{
88+
// Of course we can call everything we did with a immutable reference:
89+
takes_base(arr);
90+
91+
// However, we _can't_ call a function that takes `&mut ArrayRef`:
92+
// this would require mutable data access, which `S: Data` does not provide.
93+
//
94+
// takes_arrref_mut(arr);
95+
// rustc: cannot borrow data in dereference of `ArrayBase<S, D>` as mutable
96+
//
97+
// Nor can we call a function that takes `&mut RawRef`
98+
// takes_rawref_mut(arr);
99+
100+
// We can, however, call functions that take `AsMut<LayoutRef>`,
101+
// since `LayoutRef` does not provide read access to the data:
102+
takes_layout_mut(arr.as_layout_ref_mut());
103+
//
104+
takes_layout_asmut(arr);
60105
}
61106

62-
/// Now, an array whose data is safe to read and that we can mutate.
107+
/// Finally, let's look at a mutable reference to an `ArrayBase` with `S: DataMut`.
63108
///
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).
109+
/// Note that we require a constraint of `D: Dimension` to dereference to `&mut ArrayRef`.
66110
#[allow(dead_code)]
67-
fn takes_base_mut<S: DataMut, D: Dimension>(mut arr: ArrayBase<S, D>) -> ArrayBase<S, D>
111+
fn takes_base_data_mut<S: DataMut, D: Dimension>(arr: &mut ArrayBase<S, D>)
68112
{
69-
// Raw call
70-
arr = takes_base_raw_mut(arr);
113+
// Of course, everything we can do with just `S: Data`:
114+
takes_base_mut(arr);
115+
116+
// But also, we can now call functions that take `&mut ArrayRef`.
117+
//
118+
// NOTE: If `arr` is actually an `ArcArray` with shared data, this
119+
// will un-share the data. This can be a potentially costly operation.
120+
takes_arrref_mut(arr);
121+
}
71122

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);
123+
/// Let's now look at writing functions for the new `LayoutRef` type. We'll do this for both
124+
/// immutable and mutable references, and we'll see how there are two different ways to accept
125+
/// these types.
126+
///
127+
/// These functions can only read/modify an array's shape or strides,
128+
/// such as checking dimensionality or slicing, should take `LayoutRef`.
129+
///
130+
/// Our first way is to accept an immutable reference to `LayoutRef`:
131+
#[allow(dead_code)]
132+
fn takes_layout<A, D>(_arr: &LayoutRef<A, D>) {}
78133

79-
arr
80-
}
134+
/// We can also directly take a mutable reference to `LayoutRef`.
135+
#[allow(dead_code)]
136+
fn takes_layout_mut<A, D>(_arr: &mut LayoutRef<A, D>) {}
81137

82-
/// Now for new stuff: we want to read (but not alter) any array whose data is safe to read.
138+
/// However, the preferred way to write these functions is by accepting
139+
/// generics using `AsRef`.
83140
///
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>)
141+
/// For immutable access, writing with `AsRef` has the same benefit as usual:
142+
/// callers have nicer ergonomics, since they can just pass any type
143+
/// without having to call `.as_ref` or `.as_layout_ref`.
144+
#[allow(dead_code)]
145+
fn takes_layout_asref<T, A, D>(_arr: &T)
146+
where T: AsRef<LayoutRef<A, D>> + ?Sized
87147
{
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);
93148
}
94149

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.
150+
/// For mutable access, there is an additional reason to write with `AsMut`:
151+
/// it prevents callers who are passing in `ArcArray` or other shared array types
152+
/// from accidentally unsharing the data through a deref chain:
153+
/// `&mut ArcArray --(unshare)--> &mut ArrayRef -> &mut RawRef -> &mut LayoutRef`.
99154
#[allow(dead_code)]
100-
fn takes_arrref_mut<A, D>(arr: &mut ArrayRef<A, D>)
155+
fn takes_layout_asmut<T, A, D>(_arr: &mut T)
156+
where T: AsMut<LayoutRef<A, D>> + ?Sized
101157
{
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);
110158
}
111159

112-
/// Now, we no longer care about whether we can safely read data.
160+
/// Finally, we have `RawRef`, where we can access and mutate the array's data, but only unsafely.
161+
/// This is important for, e.g., dealing with [`std::mem::MaybeUninit`].
113162
///
114163
/// This is probably the rarest type to deal with, since `LayoutRef` can access and modify an array's
115164
/// shape and strides, and even do in-place slicing. As a result, `RawRef` is only for functionality
116165
/// that requires unsafe data access, something that `LayoutRef` can't do.
117166
///
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.
167+
/// Like `LayoutRef`, writing functions with `RawRef` can be done in a few ways.
168+
/// We start with a direct, immutable reference
124169
#[allow(dead_code)]
125170
fn takes_rawref<A, D>(arr: &RawRef<A, D>)
126171
{
127172
takes_layout(arr);
128173
takes_layout_asref(arr);
129174
}
130175

131-
/// Mutable, directly take `RawRef`
176+
/// We can also directly take a mutable reference.
132177
#[allow(dead_code)]
133178
fn takes_rawref_mut<A, D>(arr: &mut RawRef<A, D>)
134179
{
135180
takes_layout(arr);
136181
takes_layout_asmut(arr);
137182
}
138183

139-
/// Immutable, take a generic that implements `AsRef` to `RawRef`
184+
/// However, like before, the preferred way is to write with `AsRef`,
185+
/// for the same reasons as for `LayoutRef`:
140186
#[allow(dead_code)]
141187
fn takes_rawref_asref<T, A, D>(_arr: &T)
142188
where T: AsRef<RawRef<A, D>> + ?Sized
@@ -145,7 +191,7 @@ where T: AsRef<RawRef<A, D>> + ?Sized
145191
takes_layout_asref(_arr.as_ref());
146192
}
147193

148-
/// Mutable, take a generic that implements `AsMut` to `RawRef`
194+
/// Finally, mutably:
149195
#[allow(dead_code)]
150196
fn takes_rawref_asmut<T, A, D>(_arr: &mut T)
151197
where T: AsMut<RawRef<A, D>> + ?Sized
@@ -154,31 +200,4 @@ where T: AsMut<RawRef<A, D>> + ?Sized
154200
takes_layout_asmut(_arr.as_mut());
155201
}
156202

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, A, D>(_arr: &T)
173-
where T: AsRef<LayoutRef<A, D>> + ?Sized
174-
{
175-
}
176-
177-
/// Mutable, take a generic that implements `AsMut` to `LayoutRef`
178-
#[allow(dead_code)]
179-
fn takes_layout_asmut<T, A, D>(_arr: &mut T)
180-
where T: AsMut<LayoutRef<A, D>> + ?Sized
181-
{
182-
}
183-
184203
fn main() {}

src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,7 +1353,7 @@ impl<A, D> ArrayPartsSized<A, D>
13531353
/// use ndarray::{LayoutRef2, array};
13541354
///
13551355
/// fn aspect_ratio<T, A>(layout: &T) -> (usize, usize)
1356-
/// where T: AsRef<LayoutRef2<A>>
1356+
/// where T: AsRef<LayoutRef2<A>> + ?Sized
13571357
/// {
13581358
/// let layout = layout.as_ref();
13591359
/// (layout.ncols(), layout.nrows())
@@ -1380,7 +1380,7 @@ impl<A, D> ArrayPartsSized<A, D>
13801380
/// }
13811381
///
13821382
/// impl<T, A> Ratioable<A> for T
1383-
/// where T: AsRef<LayoutRef2<A>> + AsMut<LayoutRef2<A>>
1383+
/// where T: AsRef<LayoutRef2<A>> + AsMut<LayoutRef2<A>> + ?Sized
13841384
/// {
13851385
/// fn aspect_ratio(&self) -> (usize, usize)
13861386
/// {
@@ -1420,7 +1420,7 @@ impl<A, D> ArrayPartsSized<A, D>
14201420
/// expensive) guarantee that the data is uniquely held (see [`ArrayRef`]
14211421
/// for more information).
14221422
///
1423-
/// To help users avoid this error cost, functions that operate on `LayoutRef`s
1423+
/// To help users avoid this cost, functions that operate on `LayoutRef`s
14241424
/// should take their parameters as a generic type `T: AsRef<LayoutRef<A, D>>`,
14251425
/// as the above examples show. This aids the caller in two ways: they can pass
14261426
/// their arrays by reference (`&arr`) instead of explicitly calling `as_ref`,

0 commit comments

Comments
 (0)