From 73abdb89696010fe1ea6f8abb4080517c5324052 Mon Sep 17 00:00:00 2001 From: Adam Kern Date: Sun, 2 Nov 2025 13:36:45 -0500 Subject: [PATCH] Update the docs and examples for the new `Sized` implementation --- examples/functions_and_traits.rs | 227 +++++++++++++++++-------------- src/lib.rs | 6 +- 2 files changed, 126 insertions(+), 107 deletions(-) diff --git a/examples/functions_and_traits.rs b/examples/functions_and_traits.rs index 56ec1f2e2..9710a9ff5 100644 --- a/examples/functions_and_traits.rs +++ b/examples/functions_and_traits.rs @@ -7,120 +7,165 @@ //! 2. [`ArrayRef`], which represents a read-safe, uniquely-owned look at an array. //! 3. [`RawRef`], which represents a read-unsafe, possibly-shared look at an array. //! 4. [`LayoutRef`], which represents a look at an array's underlying structure, -//! but does not allow data reading of any kind. +//! but does not allow reading data of any kind. //! //! Below, we illustrate how to write functions and traits for most variants of these types. -use ndarray::{ArrayBase, ArrayRef, Data, DataMut, Dimension, LayoutRef, RawData, RawDataMut, RawRef}; +use ndarray::{ArrayBase, ArrayRef, Data, DataMut, Dimension, LayoutRef, RawRef}; -/// Take an array with the most basic requirements. +/// First, the newest pattern: this function accepts arrays whose data are safe to +/// dereference and uniquely held. /// -/// This function takes its data as owning. It is very rare that a user will need to specifically -/// take a reference to an `ArrayBase`, rather than to one of the other four types. -#[rustfmt::skip] -fn takes_base_raw(arr: ArrayBase) -> ArrayBase +/// This is probably the most common pattern for users. +/// Once we have an array reference, we can go to [`RawRef`] and [`LayoutRef`] very easily. +fn takes_arrref(arr: &ArrayRef) { - // These skip from a possibly-raw array to `RawRef` and `LayoutRef`, and so must go through `AsRef` - takes_rawref(arr.as_ref()); // Caller uses `.as_ref` - takes_rawref_asref(&arr); // Implementor uses `.as_ref` - takes_layout(arr.as_ref()); // Caller uses `.as_ref` - takes_layout_asref(&arr); // Implementor uses `.as_ref` - - arr + // Since `ArrayRef` implements `Deref` to `RawRef`, we can pass `arr` directly to a function + // that takes `RawRef`. Similarly, since `RawRef` implements `Deref` to `LayoutRef`, we can pass + // `arr` directly to a function that takes `LayoutRef`. + takes_rawref(arr); // &ArrayRef -> &RawRef + takes_layout(arr); // &ArrayRef -> &RawRef -> &LayoutRef + + // We can also pass `arr` to functions that accept `RawRef` and `LayoutRef` via `AsRef`. + // These alternative function signatures are important for other types, but we see that when + // we have an `ArrayRef`, we can call them very simply. + takes_rawref_asref(arr); // &ArrayRef -> &RawRef + takes_layout_asref(arr); // &ArrayRef -> &LayoutRef } -/// Similar to above, but allow us to read the underlying data. -#[rustfmt::skip] -fn takes_base_raw_mut(mut arr: ArrayBase) -> ArrayBase +/// Now we want any array whose data is safe to mutate. +/// +/// Importantly, any array passed to this function is guaranteed to uniquely point to its data. +/// As a result, passing a shared array to this function will silently un-share the array. +/// So, ***users should only accept `&mut ArrayRef` when they want to mutate data***. +/// If they just want to mutate shape and strides, use `&mut LayoutRef` or `&AsMut`. +#[allow(dead_code)] +fn takes_arrref_mut(arr: &mut ArrayRef) { - // These skip from a possibly-raw array to `RawRef` and `LayoutRef`, and so must go through `AsMut` - takes_rawref_mut(arr.as_mut()); // Caller uses `.as_mut` - takes_rawref_asmut(&mut arr); // Implementor uses `.as_mut` - takes_layout_mut(arr.as_mut()); // Caller uses `.as_mut` - takes_layout_asmut(&mut arr); // Implementor uses `.as_mut` + // We can do everything we did with a `&ArrayRef` + takes_arrref(arr); - arr + // Similarly, we can pass this to functions that accept mutable references + // to our other array reference types. These first two happen via `Deref`... + takes_rawref_mut(arr); + takes_layout_mut(arr); + + // ... and these two happen via `AsRef`. + takes_rawref_asmut(arr); + takes_rawref_asmut(arr); } -/// Now take an array whose data is safe to read. +/// Now let's go back and look at the way to write functions prior to 0.17: using `ArrayBase`. +/// +/// This function signature says three things: +/// 1. Let me take a read only reference (that's the `&`) +/// 2. Of an array whose data is safe to dereference (that's the `S: Data`) +/// 3. And whose data is read-only (also `S: Data`) +/// +/// Let's see what we can do with this array: #[allow(dead_code)] -fn takes_base(mut arr: ArrayBase) -> ArrayBase +fn takes_base(arr: &ArrayBase) { - // Raw call - arr = takes_base_raw(arr); + // First off: we can pass it to functions that accept `&ArrayRef`. + // + // This is always "cheap", in the sense that even if `arr` is an + // `ArcArray` that shares its data, using this call will not un-share that data. + takes_arrref(arr); - // No need for AsRef, since data is safe - takes_arrref(&arr); + // We can also pass it to functions that accept `RawRef` and `LayoutRef` + // in the usual two ways: takes_rawref(&arr); - takes_rawref_asref(&arr); takes_layout(&arr); + // + takes_rawref_asref(&arr); takes_layout_asref(&arr); +} - arr +/// Now, let's take a mutable reference to an `ArrayBase` - but let's keep `S: Data`, such +/// that we are allowed to change the _layout_ of the array, but not its data. +fn takes_base_mut(arr: &mut ArrayBase) +{ + // Of course we can call everything we did with a immutable reference: + takes_base(arr); + + // However, we _can't_ call a function that takes `&mut ArrayRef`: + // this would require mutable data access, which `S: Data` does not provide. + // + // takes_arrref_mut(arr); + // rustc: cannot borrow data in dereference of `ArrayBase` as mutable + // + // Nor can we call a function that takes `&mut RawRef` + // takes_rawref_mut(arr); + + // We can, however, call functions that take `AsMut`, + // since `LayoutRef` does not provide read access to the data: + takes_layout_mut(arr.as_layout_ref_mut()); + // + takes_layout_asmut(arr); } -/// Now, an array whose data is safe to read and that we can mutate. +/// Finally, let's look at a mutable reference to an `ArrayBase` with `S: DataMut`. /// -/// Notice that we include now a trait bound on `D: Dimension`; this is necessary in order -/// for the `ArrayBase` to dereference to an `ArrayRef` (or to any of the other types). +/// Note that we require a constraint of `D: Dimension` to dereference to `&mut ArrayRef`. #[allow(dead_code)] -fn takes_base_mut(mut arr: ArrayBase) -> ArrayBase +fn takes_base_data_mut(arr: &mut ArrayBase) { - // Raw call - arr = takes_base_raw_mut(arr); + // Of course, everything we can do with just `S: Data`: + takes_base_mut(arr); + + // But also, we can now call functions that take `&mut ArrayRef`. + // + // NOTE: If `arr` is actually an `ArcArray` with shared data, this + // will un-share the data. This can be a potentially costly operation. + takes_arrref_mut(arr); +} - // No need for AsMut, since data is safe - takes_arrref_mut(&mut arr); - takes_rawref_mut(&mut arr); - takes_rawref_asmut(&mut arr); - takes_layout_mut(&mut arr); - takes_layout_asmut(&mut arr); +/// Let's now look at writing functions for the new `LayoutRef` type. We'll do this for both +/// immutable and mutable references, and we'll see how there are two different ways to accept +/// these types. +/// +/// These functions can only read/modify an array's shape or strides, +/// such as checking dimensionality or slicing, should take `LayoutRef`. +/// +/// Our first way is to accept an immutable reference to `LayoutRef`: +#[allow(dead_code)] +fn takes_layout(_arr: &LayoutRef) {} - arr -} +/// We can also directly take a mutable reference to `LayoutRef`. +#[allow(dead_code)] +fn takes_layout_mut(_arr: &mut LayoutRef) {} -/// Now for new stuff: we want to read (but not alter) any array whose data is safe to read. +/// However, the preferred way to write these functions is by accepting +/// generics using `AsRef`. /// -/// This is probably the most common functionality that one would want to write. -/// As we'll see below, calling this function is very simple for `ArrayBase`. -fn takes_arrref(arr: &ArrayRef) +/// For immutable access, writing with `AsRef` has the same benefit as usual: +/// callers have nicer ergonomics, since they can just pass any type +/// without having to call `.as_ref` or `.as_layout_ref`. +#[allow(dead_code)] +fn takes_layout_asref(_arr: &T) +where T: AsRef> + ?Sized { - // No need for AsRef, since data is safe - takes_rawref(arr); - takes_rawref_asref(arr); - takes_layout(arr); - takes_layout_asref(arr); } -/// Now we want any array whose data is safe to mutate. -/// -/// **Importantly**, any array passed to this function is guaranteed to uniquely point to its data. -/// As a result, passing a shared array to this function will **silently** un-share the array. +/// For mutable access, there is an additional reason to write with `AsMut`: +/// it prevents callers who are passing in `ArcArray` or other shared array types +/// from accidentally unsharing the data through a deref chain: +/// `&mut ArcArray --(unshare)--> &mut ArrayRef -> &mut RawRef -> &mut LayoutRef`. #[allow(dead_code)] -fn takes_arrref_mut(arr: &mut ArrayRef) +fn takes_layout_asmut(_arr: &mut T) +where T: AsMut> + ?Sized { - // Immutable call - takes_arrref(arr); - - // No need for AsMut, since data is safe - takes_rawref_mut(arr); - takes_rawref_asmut(arr); - takes_layout_mut(arr); - takes_rawref_asmut(arr); } -/// Now, we no longer care about whether we can safely read data. +/// Finally, we have `RawRef`, where we can access and mutate the array's data, but only unsafely. +/// This is important for, e.g., dealing with [`std::mem::MaybeUninit`]. /// /// This is probably the rarest type to deal with, since `LayoutRef` can access and modify an array's /// shape and strides, and even do in-place slicing. As a result, `RawRef` is only for functionality /// that requires unsafe data access, something that `LayoutRef` can't do. /// -/// Writing functions and traits that deal with `RawRef`s and `LayoutRef`s can be done two ways: -/// 1. Directly on the types; calling these functions on arrays whose data are not known to be safe -/// to dereference (i.e., raw array views or `ArrayBase`) must explicitly call `.as_ref()`. -/// 2. Via a generic with `: AsRef>`; doing this will allow direct calling for all `ArrayBase` and -/// `ArrayRef` instances. -/// We'll demonstrate #1 here for both immutable and mutable references, then #2 directly below. +/// Like `LayoutRef`, writing functions with `RawRef` can be done in a few ways. +/// We start with a direct, immutable reference #[allow(dead_code)] fn takes_rawref(arr: &RawRef) { @@ -128,7 +173,7 @@ fn takes_rawref(arr: &RawRef) takes_layout_asref(arr); } -/// Mutable, directly take `RawRef` +/// We can also directly take a mutable reference. #[allow(dead_code)] fn takes_rawref_mut(arr: &mut RawRef) { @@ -136,7 +181,8 @@ fn takes_rawref_mut(arr: &mut RawRef) takes_layout_asmut(arr); } -/// Immutable, take a generic that implements `AsRef` to `RawRef` +/// However, like before, the preferred way is to write with `AsRef`, +/// for the same reasons as for `LayoutRef`: #[allow(dead_code)] fn takes_rawref_asref(_arr: &T) where T: AsRef> + ?Sized @@ -145,7 +191,7 @@ where T: AsRef> + ?Sized takes_layout_asref(_arr.as_ref()); } -/// Mutable, take a generic that implements `AsMut` to `RawRef` +/// Finally, mutably: #[allow(dead_code)] fn takes_rawref_asmut(_arr: &mut T) where T: AsMut> + ?Sized @@ -154,31 +200,4 @@ where T: AsMut> + ?Sized takes_layout_asmut(_arr.as_mut()); } -/// Finally, there's `LayoutRef`: this type provides read and write access to an array's *structure*, but not its *data*. -/// -/// Practically, this means that functions that only read/modify an array's shape or strides, -/// such as checking dimensionality or slicing, should take `LayoutRef`. -/// -/// Like `RawRef`, functions can be written either directly on `LayoutRef` or as generics with `: AsRef>>`. -#[allow(dead_code)] -fn takes_layout(_arr: &LayoutRef) {} - -/// Mutable, directly take `LayoutRef` -#[allow(dead_code)] -fn takes_layout_mut(_arr: &mut LayoutRef) {} - -/// Immutable, take a generic that implements `AsRef` to `LayoutRef` -#[allow(dead_code)] -fn takes_layout_asref(_arr: &T) -where T: AsRef> + ?Sized -{ -} - -/// Mutable, take a generic that implements `AsMut` to `LayoutRef` -#[allow(dead_code)] -fn takes_layout_asmut(_arr: &mut T) -where T: AsMut> + ?Sized -{ -} - fn main() {} diff --git a/src/lib.rs b/src/lib.rs index 9f2c53d79..baa62ca5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1353,7 +1353,7 @@ impl ArrayPartsSized /// use ndarray::{LayoutRef2, array}; /// /// fn aspect_ratio(layout: &T) -> (usize, usize) -/// where T: AsRef> +/// where T: AsRef> + ?Sized /// { /// let layout = layout.as_ref(); /// (layout.ncols(), layout.nrows()) @@ -1380,7 +1380,7 @@ impl ArrayPartsSized /// } /// /// impl Ratioable for T -/// where T: AsRef> + AsMut> +/// where T: AsRef> + AsMut> + ?Sized /// { /// fn aspect_ratio(&self) -> (usize, usize) /// { @@ -1420,7 +1420,7 @@ impl ArrayPartsSized /// expensive) guarantee that the data is uniquely held (see [`ArrayRef`] /// for more information). /// -/// To help users avoid this error cost, functions that operate on `LayoutRef`s +/// To help users avoid this cost, functions that operate on `LayoutRef`s /// should take their parameters as a generic type `T: AsRef>`, /// as the above examples show. This aids the caller in two ways: they can pass /// their arrays by reference (`&arr`) instead of explicitly calling `as_ref`,