From 0f24c9434b03909220ada20ca3173864ce1cee83 Mon Sep 17 00:00:00 2001 From: Adam Kern Date: Sat, 14 Feb 2026 09:39:02 -0500 Subject: [PATCH 1/6] Working towards a shape trait --- src/layout/dyn_repr.rs | 214 +++++++++++++++++++++++++++++ src/layout/mod.rs | 47 +++++++ src/layout/n_repr.rs | 298 +++++++++++++++++++++++++++++++++++++++++ src/layout/shape.rs | 83 ++++++++++++ 4 files changed, 642 insertions(+) create mode 100644 src/layout/dyn_repr.rs create mode 100644 src/layout/n_repr.rs create mode 100644 src/layout/shape.rs diff --git a/src/layout/dyn_repr.rs b/src/layout/dyn_repr.rs new file mode 100644 index 000000000..dcc6060dd --- /dev/null +++ b/src/layout/dyn_repr.rs @@ -0,0 +1,214 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; +use core::ops::{Deref, DerefMut, Index, IndexMut}; +use core::slice::Iter; +use num_traits::Zero; + +use crate::layout::rank::DynRank; +use crate::layout::ranked::Ranked; +use crate::layout::shape::{IntoShape, Shape}; +use crate::Axis; + +const CAP: usize = 4; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DynAxesRepr +{ + Inline(usize, [T; CAP]), + Alloc(Box<[T]>), +} + +pub type DShape = DynAxesRepr; + +impl DynAxesRepr +{ + fn ndim(&self) -> usize + { + match self { + DynAxesRepr::Inline(len, _) => *len, + DynAxesRepr::Alloc(items) => items.len(), + } + } +} + +impl Deref for DynAxesRepr +{ + type Target = [T]; + + fn deref(&self) -> &Self::Target + { + match self { + DynAxesRepr::Inline(len, arr) => { + debug_assert!(*len <= arr.len()); + unsafe { arr.get_unchecked(..*len) } + } + DynAxesRepr::Alloc(items) => items, + } + } +} + +impl DerefMut for DynAxesRepr +{ + fn deref_mut(&mut self) -> &mut Self::Target + { + todo!() + } +} + +impl Index for DynAxesRepr +{ + type Output = T; + + fn index(&self, index: usize) -> &Self::Output + { + &(**self)[index] + } +} + +impl IndexMut for DynAxesRepr +{ + fn index_mut(&mut self, index: usize) -> &mut Self::Output + { + &mut (**self)[index] + } +} + +impl Index for DynAxesRepr +{ + type Output = T; + + fn index(&self, index: Axis) -> &Self::Output + { + self.index(index.0) + } +} + +impl IndexMut for DynAxesRepr +{ + fn index_mut(&mut self, index: Axis) -> &mut Self::Output + { + self.index_mut(index.0) + } +} + +impl Default for DynAxesRepr +where T: Zero + Copy +{ + fn default() -> Self + { + Self::Inline(1, [T::zero(); 4]) + } +} + +impl Zero for DShape +{ + fn zero() -> Self + { + Self::Inline(1, [0usize; CAP]) + } + + fn is_zero(&self) -> bool + { + self.iter().all(|e| e.is_zero()) + } + + fn set_zero(&mut self) + { + self.iter_mut().for_each(|e| e.set_zero()); + } +} + +impl From for DynAxesRepr +where + Rhs: AsRef<[T]>, + T: Default + Copy, +{ + fn from(value: Rhs) -> Self + { + let value = value.as_ref(); + let n = value.len(); + if n <= CAP { + let mut inline = [T::default(); CAP]; + inline.split_at_mut(n).0.copy_from_slice(value); + Self::Inline(n, inline) + } else { + Self::Alloc(value.into()) + } + } +} + +impl Ranked for DynAxesRepr +{ + type NDim = DynRank; + + fn ndim(&self) -> usize + { + match self { + DynAxesRepr::Inline(d, _) => d.clone(), + DynAxesRepr::Alloc(items) => items.len(), + } + } +} + +macro_rules! impl_op { + ($op_trait:ty, $op_fn:ident, $op_assign_trait:ty, $op_assign_fn:ident) => { + /// *Panics* if the two dimensionalities are different + impl $op_trait for DShape + where + Rhs: IntoShape, + { + type Output = DShape; + + fn $op_fn(self, rhs: Rhs) -> ::Output { + let mut output = self.clone(); + output.$op_assign_fn(rhs); + output + } + } + + /// *Panics* if the two dimensionalities are different + impl $op_assign_trait for DShape + where + Rhs: IntoShape, + { + fn $op_assign_fn(&mut self, rhs: Rhs) { + let other = rhs.into_shape(); + for i in 0..self.ndim().max(other.ndim()) { + self[i].$op_assign_fn(other[i]); + } + } + } + }; +} + +impl_op!(Add, add, AddAssign, add_assign); +impl_op!(Sub, sub, SubAssign, sub_assign); +impl_op!(Mul, mul, MulAssign, mul_assign); + +impl IntoIterator for DynAxesRepr +where T: Clone +{ + type Item = T; + type IntoIter = alloc::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter + { + match self { + DynAxesRepr::Inline(len, arr) => Vec::from(arr[..len].to_vec()).into_iter(), + DynAxesRepr::Alloc(b) => b.into_vec().into_iter(), + } + } +} + +impl Shape for DynAxesRepr +{ + type Iter<'a> + = Iter<'a, usize> + where Self: 'a; + + fn iter(&self) -> Self::Iter<'_> + { + (**self).iter() + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 9f5d1e4e1..acdfc33a0 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -11,6 +11,53 @@ mod bitset; pub mod rank; pub mod ranked; +pub mod shape; + +use core::any::type_name; +use core::error::Error; +use core::fmt::{Debug, Display}; +use core::marker::PhantomData; + +use crate::layout::ranked::Ranked; #[allow(deprecated)] pub use bitset::{Layout, LayoutBitset}; + +/// The error type for dealing with shapes and strides +#[derive(Debug, Clone, Copy)] +pub enum ShapeStrideError +{ + /// Out of bounds; specifically, using an index that is larger than the dimensionality of the shape or strides `S`. + OutOfBounds(PhantomData, usize), + /// The error when trying to construct or mutate a shape or strides with the wrong dimensionality value. + RankMismatch(PhantomData, usize), + /// The desired shape would represent an array with more elements than `usize::MAX` + ShapeOverflow, +} + +impl ShapeStrideError +{ + pub fn replace_type_with(&self) -> ShapeStrideError + { + match self { + ShapeStrideError::OutOfBounds(_, u) => ShapeStrideError::OutOfBounds(PhantomData, *u), + ShapeStrideError::RankMismatch(_, u) => ShapeStrideError::RankMismatch(PhantomData, *u), + ShapeStrideError::ShapeOverflow => ShapeStrideError::ShapeOverflow, + } + } +} + +impl Display for ShapeStrideError +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result + { + match self { + ShapeStrideError::OutOfBounds(_, idx) => + write!(f, "Index {idx} is larger than the dimensionality of {}", type_name::()), + ShapeStrideError::RankMismatch(_, rank) => write!(f, "{} has a rank of {}, which is incompatible with requested rank of {rank}", type_name::(), type_name::()), + ShapeStrideError::ShapeOverflow => write!(f, "The desired shape would represent an array with more elements than `usize::MAX`") + } + } +} + +impl Error for ShapeStrideError {} diff --git a/src/layout/n_repr.rs b/src/layout/n_repr.rs new file mode 100644 index 000000000..de1b3584e --- /dev/null +++ b/src/layout/n_repr.rs @@ -0,0 +1,298 @@ +use core::{ + array::IntoIter, + marker::PhantomData, + ops::{Add, AddAssign, Deref, DerefMut, Index, IndexMut, Mul, MulAssign, Sub, SubAssign}, +}; +use std::slice::Iter; + +use num_traits::Zero; + +use crate::layout::{ + rank::{ConstRank, Rank}, + ranked::Ranked, + shape::{IntoShape, Shape}, + ShapeStrideError, +}; + +/// A wrapper for fixed-length arrays that can be used for shape and strides. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ShapeStrideN([T; N]); + +pub type NShape = ShapeStrideN; + +impl Deref for ShapeStrideN +{ + type Target = [T; N]; + + fn deref(&self) -> &Self::Target + { + &self.0 + } +} + +impl DerefMut for ShapeStrideN +{ + fn deref_mut(&mut self) -> &mut Self::Target + { + &mut self.0 + } +} + +impl Index for ShapeStrideN +{ + type Output = T; + + fn index(&self, index: usize) -> &Self::Output + { + &self.0[index] + } +} + +impl IndexMut for ShapeStrideN +{ + fn index_mut(&mut self, index: usize) -> &mut Self::Output + { + &mut self.0[index] + } +} + +impl From<[T; N]> for ShapeStrideN +{ + fn from(value: [T; N]) -> Self + { + Self { 0: value } + } +} + +impl From> for [T; N] +{ + fn from(value: ShapeStrideN) -> Self + { + value.0 + } +} + +impl<'a, T, const N: usize> TryFrom<&'a [T]> for ShapeStrideN +where T: Copy + Zero +{ + type Error = ShapeStrideError; + + fn try_from(value: &'a [T]) -> Result + { + if value.len() != N { + Err(ShapeStrideError::BadDimality(PhantomData, value.len())) + } else { + let mut arr = [T::zero(); N]; + arr.copy_from_slice(value); + Ok(Self { 0: arr }) + } + } +} + +impl IntoIterator for ShapeStrideN +{ + type Item = T; + + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter + { + self.0.into_iter() + } +} + +macro_rules! shapestride_and_tuples { + ($(($tuple:ty, $N:literal)),*) => { + $( + impl From<$tuple> for ShapeStrideN + { + fn from(value: $tuple) -> Self + { + Self { 0: value.into() } + } + } + + impl From> for $tuple + { + fn from(value: ShapeStrideN) -> Self + { + value.0.into() + } + } + + impl IntoShape for $tuple + where + NShape<$N>: From<$tuple>, + T: Copy, + { + type NDim = ConstRank<$N>; + + type Shape = NShape<$N>; + + fn into_shape(&self) -> Self::Shape + { + (*self).into() + } + } + )* + }; +} + +shapestride_and_tuples!( + ((T,), 1), + ((T, T), 2), + ((T, T, T), 3), + ((T, T, T, T), 4), + ((T, T, T, T, T), 5), + ((T, T, T, T, T, T), 6), + ((T, T, T, T, T, T, T), 7), + ((T, T, T, T, T, T, T, T), 8), + ((T, T, T, T, T, T, T, T, T), 9), + ((T, T, T, T, T, T, T, T, T, T), 10), + ((T, T, T, T, T, T, T, T, T, T, T), 11), + ((T, T, T, T, T, T, T, T, T, T, T, T), 12) +); + +impl PartialEq for ShapeStrideN +where + T: PartialEq, + Rhs: AsRef<[T]>, +{ + fn eq(&self, other: &Rhs) -> bool + { + let other = other.as_ref(); + if other.len() != N { + return false; + } + for i in 0..N { + if self[i] != other[i] { + return false; + } + } + return true; + } +} + +impl Ranked for ShapeStrideN +where ConstRank: Rank +{ + type NDim = ConstRank; + + fn ndim(&self) -> usize + { + N + } +} + +impl IntoShape for [usize; N] +where ConstRank: Rank +{ + type NDim = ConstRank; + + type Shape = NShape; + + fn into_shape(&self) -> Self::Shape + { + (*self).into() + } +} + +macro_rules! impl_op { + ($op_trait:ty, $op_fn:ident, $op_assign_trait:ty, $op_assign_fn:ident) => { + impl $op_trait for NShape + where + Rhs: IntoShape>, + { + type Output = NShape; + + fn $op_fn(self, rhs: Rhs) -> Self::Output { + let mut output = self.clone(); + output.$op_assign_fn(rhs); + output + } + } + + impl $op_assign_trait for NShape + where + Rhs: IntoShape>, + { + fn $op_assign_fn(&mut self, rhs: Rhs) { + let other = rhs.into_shape(); + for i in 0..N { + self[i].$op_assign_fn(other[i]); + } + } + } + }; +} + +impl_op!(Add, add, AddAssign, add_assign); +impl_op!(Sub, sub, SubAssign, sub_assign); +impl_op!(Mul, mul, MulAssign, mul_assign); + +impl Add for NShape +{ + type Output = NShape; + + fn add(self, rhs: usize) -> Self::Output + { + let mut output = self.clone(); + output += rhs; + output + } +} + +impl AddAssign for NShape +{ + fn add_assign(&mut self, rhs: usize) + { + for o in self.iter_mut() { + *o += rhs; + } + } +} + +impl Mul for NShape +{ + type Output = NShape; + + fn mul(self, rhs: usize) -> Self::Output + { + let mut output = self.clone(); + for o in output.iter_mut() { + *o = *o * rhs; + } + output + } +} + +impl MulAssign for NShape +{ + fn mul_assign(&mut self, rhs: usize) + { + for o in self.iter_mut() { + *o += rhs; + } + } +} + +impl Shape for NShape +where ConstRank: Rank +{ + type Iter<'a> + = Iter<'a, usize> + where Self: 'a; + + fn iter(&self) -> Self::Iter<'_> + { + self.0.iter() + } +} + +impl Default for NShape +{ + fn default() -> Self + { + Self([0; N]) + } +} diff --git a/src/layout/shape.rs b/src/layout/shape.rs new file mode 100644 index 000000000..5a84e11d8 --- /dev/null +++ b/src/layout/shape.rs @@ -0,0 +1,83 @@ +use core::iter::Map; + +use crate::layout::rank::Rank; +use crate::layout::ranked::Ranked; + +pub trait Shape: Ranked +{ + type Iter<'a>: Iterator + ExactSizeIterator + DoubleEndedIterator + where Self: 'a; + + type Pattern: Ranked; + + /// The length of the array along a given axis. + fn axis_len(&self, axis: usize) -> usize; + + /// Iterate over the dimensions of the shape. + fn iter(&self) -> Self::Iter<'_>; + + /// Get the number of elements that the array contains. + /// + /// If the number of elements is greater than `isize::MAX`, returns `None`. + /// + /// # Safety + /// This method checks for overflow past `isize`, not `usize`. If this method returns `Some(_)`, + /// then users know they can safely cast all dimensions of the shape to `isize`, which is often + /// necessary for calculations with strides and offsets. + fn size_checked(&self) -> Option + { + self.iter() + .try_fold(1_usize, |acc, &i| acc.checked_mul(i)) + .and_then(as_usize_if_isize_compatible) + } + + /// Get the number of bytes that this array would fill. + /// + /// # Safety + /// This method checks for bytes overflow past `isize`. If this method returns `Some(_)`, + /// then users know that an allocated array is indexable using `isize` offsets. + fn size_bytes_checked(&self) -> Option + { + self.size_checked() + .and_then(|v| v.checked_mul(size_of::())) + .and_then(as_usize_if_isize_compatible) + } + + fn iter_isize<'a>(&'a self) -> Option, impl FnMut(&'a usize) -> isize>> + { + self.size_checked() + .map(|_| self.iter().map(|v| *v as isize)) + } +} + +fn as_usize_if_isize_compatible(v: usize) -> Option +{ + if v <= (isize::MAX as usize) { + Some(v) + } else { + None + } +} + +/// A conversion trait for types that can turn into a shape. +pub trait IntoShape +{ + type NDim: Rank; + + type Shape: Shape; + + fn into_shape(&self) -> Self::Shape; +} + +impl IntoShape for T +where T: Shape +{ + type NDim = T::NDim; + + type Shape = T; + + fn into_shape(&self) -> Self::Shape + { + self.clone() + } +} From b7d4a73730f71260dfff8cbf3806c5a8861218f6 Mon Sep 17 00:00:00 2001 From: Adam Kern Date: Sun, 15 Feb 2026 10:11:57 -0500 Subject: [PATCH 2/6] Move to usize, not &usize --- src/layout/shape.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/layout/shape.rs b/src/layout/shape.rs index 5a84e11d8..83d2fa2b4 100644 --- a/src/layout/shape.rs +++ b/src/layout/shape.rs @@ -1,20 +1,26 @@ +use core::array; use core::iter::Map; -use crate::layout::rank::Rank; +use crate::layout::rank::{ConstRank, DynRank, Rank}; use crate::layout::ranked::Ranked; pub trait Shape: Ranked { - type Iter<'a>: Iterator + ExactSizeIterator + DoubleEndedIterator - where Self: 'a; + type Iter: Iterator + ExactSizeIterator + DoubleEndedIterator; + /// A representation of the shape as a type that can be destructured. + /// + /// For example, for 2D shapes, this should be something like [usize; 2]. + /// For 3D shapes, [usize; 3], etc. + /// For shapes whose rank isn't known at compile time, this can be the marker + /// type [`DMarker`]. type Pattern: Ranked; /// The length of the array along a given axis. fn axis_len(&self, axis: usize) -> usize; /// Iterate over the dimensions of the shape. - fn iter(&self) -> Self::Iter<'_>; + fn iter(&self) -> Self::Iter; /// Get the number of elements that the array contains. /// @@ -27,7 +33,7 @@ pub trait Shape: Ranked fn size_checked(&self) -> Option { self.iter() - .try_fold(1_usize, |acc, &i| acc.checked_mul(i)) + .try_fold(1_usize, |acc, i| acc.checked_mul(i)) .and_then(as_usize_if_isize_compatible) } @@ -43,10 +49,9 @@ pub trait Shape: Ranked .and_then(as_usize_if_isize_compatible) } - fn iter_isize<'a>(&'a self) -> Option, impl FnMut(&'a usize) -> isize>> + fn iter_isize<'a>(&'a self) -> Option isize>> { - self.size_checked() - .map(|_| self.iter().map(|v| *v as isize)) + self.size_checked().map(|_| self.iter().map(|v| v as isize)) } } @@ -59,6 +64,13 @@ fn as_usize_if_isize_compatible(v: usize) -> Option } } +pub trait Patterned +{ + type Pattern; + + fn into_pattern(&self) -> Self::Pattern; +} + /// A conversion trait for types that can turn into a shape. pub trait IntoShape { @@ -70,7 +82,7 @@ pub trait IntoShape } impl IntoShape for T -where T: Shape +where T: Shape + Clone { type NDim = T::NDim; From bf4a7a4896462c97f50e93fcd88a4c17631b1c85 Mon Sep 17 00:00:00 2001 From: Adam Kern Date: Sat, 21 Feb 2026 14:38:34 -0500 Subject: [PATCH 3/6] Minimal shape proposal --- src/layout/mod.rs | 19 ++++----------- src/layout/n_repr.rs | 52 +++++++++++----------------------------- src/layout/shape.rs | 57 +++++++------------------------------------- 3 files changed, 28 insertions(+), 100 deletions(-) diff --git a/src/layout/mod.rs b/src/layout/mod.rs index acdfc33a0..df3ef6b9e 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -11,7 +11,8 @@ mod bitset; pub mod rank; pub mod ranked; -pub mod shape; +mod shape; +mod n_repr; use core::any::type_name; use core::error::Error; @@ -22,6 +23,8 @@ use crate::layout::ranked::Ranked; #[allow(deprecated)] pub use bitset::{Layout, LayoutBitset}; +pub use n_repr::NShape; +pub use shape::Shape; /// The error type for dealing with shapes and strides #[derive(Debug, Clone, Copy)] @@ -31,22 +34,10 @@ pub enum ShapeStrideError OutOfBounds(PhantomData, usize), /// The error when trying to construct or mutate a shape or strides with the wrong dimensionality value. RankMismatch(PhantomData, usize), - /// The desired shape would represent an array with more elements than `usize::MAX` + /// The desired shape would represent an array with more elements than `isize::MAX` ShapeOverflow, } -impl ShapeStrideError -{ - pub fn replace_type_with(&self) -> ShapeStrideError - { - match self { - ShapeStrideError::OutOfBounds(_, u) => ShapeStrideError::OutOfBounds(PhantomData, *u), - ShapeStrideError::RankMismatch(_, u) => ShapeStrideError::RankMismatch(PhantomData, *u), - ShapeStrideError::ShapeOverflow => ShapeStrideError::ShapeOverflow, - } - } -} - impl Display for ShapeStrideError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result diff --git a/src/layout/n_repr.rs b/src/layout/n_repr.rs index de1b3584e..bde3d5234 100644 --- a/src/layout/n_repr.rs +++ b/src/layout/n_repr.rs @@ -3,14 +3,14 @@ use core::{ marker::PhantomData, ops::{Add, AddAssign, Deref, DerefMut, Index, IndexMut, Mul, MulAssign, Sub, SubAssign}, }; -use std::slice::Iter; +use std::{iter::Cloned, slice::Iter}; use num_traits::Zero; use crate::layout::{ rank::{ConstRank, Rank}, ranked::Ranked, - shape::{IntoShape, Shape}, + shape::Shape, ShapeStrideError, }; @@ -18,6 +18,7 @@ use crate::layout::{ #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ShapeStrideN([T; N]); +/// An array shape with a constant rank. pub type NShape = ShapeStrideN; impl Deref for ShapeStrideN @@ -80,7 +81,7 @@ where T: Copy + Zero fn try_from(value: &'a [T]) -> Result { if value.len() != N { - Err(ShapeStrideError::BadDimality(PhantomData, value.len())) + Err(ShapeStrideError::RankMismatch(PhantomData, value.len())) } else { let mut arr = [T::zero(); N]; arr.copy_from_slice(value); @@ -119,21 +120,6 @@ macro_rules! shapestride_and_tuples { value.0.into() } } - - impl IntoShape for $tuple - where - NShape<$N>: From<$tuple>, - T: Copy, - { - type NDim = ConstRank<$N>; - - type Shape = NShape<$N>; - - fn into_shape(&self) -> Self::Shape - { - (*self).into() - } - } )* }; } @@ -184,24 +170,11 @@ where ConstRank: Rank } } -impl IntoShape for [usize; N] -where ConstRank: Rank -{ - type NDim = ConstRank; - - type Shape = NShape; - - fn into_shape(&self) -> Self::Shape - { - (*self).into() - } -} - macro_rules! impl_op { ($op_trait:ty, $op_fn:ident, $op_assign_trait:ty, $op_assign_fn:ident) => { impl $op_trait for NShape where - Rhs: IntoShape>, + Rhs: Into>, { type Output = NShape; @@ -214,10 +187,10 @@ macro_rules! impl_op { impl $op_assign_trait for NShape where - Rhs: IntoShape>, + Rhs: Into>, { fn $op_assign_fn(&mut self, rhs: Rhs) { - let other = rhs.into_shape(); + let other = rhs.into(); for i in 0..N { self[i].$op_assign_fn(other[i]); } @@ -279,13 +252,16 @@ impl MulAssign for NShape impl Shape for NShape where ConstRank: Rank { - type Iter<'a> - = Iter<'a, usize> - where Self: 'a; + type Iter<'a> = Cloned>; + + fn axis_len(&self, axis: usize) -> usize + { + self.0[axis] + } fn iter(&self) -> Self::Iter<'_> { - self.0.iter() + self.0.iter().cloned() } } diff --git a/src/layout/shape.rs b/src/layout/shape.rs index 83d2fa2b4..b16398d63 100644 --- a/src/layout/shape.rs +++ b/src/layout/shape.rs @@ -1,35 +1,23 @@ -use core::array; use core::iter::Map; -use crate::layout::rank::{ConstRank, DynRank, Rank}; use crate::layout::ranked::Ranked; +/// A trait for array shapes. pub trait Shape: Ranked { - type Iter: Iterator + ExactSizeIterator + DoubleEndedIterator; - - /// A representation of the shape as a type that can be destructured. - /// - /// For example, for 2D shapes, this should be something like [usize; 2]. - /// For 3D shapes, [usize; 3], etc. - /// For shapes whose rank isn't known at compile time, this can be the marker - /// type [`DMarker`]. - type Pattern: Ranked; + /// The iterator type over the dimensions of the shape. + type Iter<'a>: Iterator + ExactSizeIterator + DoubleEndedIterator + where Self: 'a; /// The length of the array along a given axis. fn axis_len(&self, axis: usize) -> usize; /// Iterate over the dimensions of the shape. - fn iter(&self) -> Self::Iter; + fn iter(&self) -> Self::Iter<'_>; /// Get the number of elements that the array contains. /// /// If the number of elements is greater than `isize::MAX`, returns `None`. - /// - /// # Safety - /// This method checks for overflow past `isize`, not `usize`. If this method returns `Some(_)`, - /// then users know they can safely cast all dimensions of the shape to `isize`, which is often - /// necessary for calculations with strides and offsets. fn size_checked(&self) -> Option { self.iter() @@ -49,7 +37,10 @@ pub trait Shape: Ranked .and_then(as_usize_if_isize_compatible) } - fn iter_isize<'a>(&'a self) -> Option isize>> + /// Iterate over the shape as `isize`. + /// + /// If the number of elements is greater than `isize::MAX`, returns `None`. + fn iter_isize<'a>(&'a self) -> Option, impl FnMut(usize) -> isize>> { self.size_checked().map(|_| self.iter().map(|v| v as isize)) } @@ -63,33 +54,3 @@ fn as_usize_if_isize_compatible(v: usize) -> Option None } } - -pub trait Patterned -{ - type Pattern; - - fn into_pattern(&self) -> Self::Pattern; -} - -/// A conversion trait for types that can turn into a shape. -pub trait IntoShape -{ - type NDim: Rank; - - type Shape: Shape; - - fn into_shape(&self) -> Self::Shape; -} - -impl IntoShape for T -where T: Shape + Clone -{ - type NDim = T::NDim; - - type Shape = T; - - fn into_shape(&self) -> Self::Shape - { - self.clone() - } -} From 2e07be29ad2c1de3f6e418d2975b9290de4fa090 Mon Sep 17 00:00:00 2001 From: Adam Kern Date: Sat, 21 Feb 2026 14:39:50 -0500 Subject: [PATCH 4/6] Import rearranging --- src/layout/n_repr.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/layout/n_repr.rs b/src/layout/n_repr.rs index bde3d5234..2d9afaaf5 100644 --- a/src/layout/n_repr.rs +++ b/src/layout/n_repr.rs @@ -1,9 +1,10 @@ use core::{ array::IntoIter, + iter::Cloned, marker::PhantomData, ops::{Add, AddAssign, Deref, DerefMut, Index, IndexMut, Mul, MulAssign, Sub, SubAssign}, + slice::Iter, }; -use std::{iter::Cloned, slice::Iter}; use num_traits::Zero; From bcfef7a9b88348da556ed0e19e4534664b6bf86c Mon Sep 17 00:00:00 2001 From: Adam Kern Date: Sun, 22 Feb 2026 10:03:02 -0500 Subject: [PATCH 5/6] Add dynamic shape Also add a Zero impl for NShape --- src/layout/dyn_repr.rs | 66 +++++++++++++----------------------------- src/layout/mod.rs | 2 ++ src/layout/n_repr.rs | 15 +++++++++- 3 files changed, 36 insertions(+), 47 deletions(-) diff --git a/src/layout/dyn_repr.rs b/src/layout/dyn_repr.rs index dcc6060dd..f09892d79 100644 --- a/src/layout/dyn_repr.rs +++ b/src/layout/dyn_repr.rs @@ -1,13 +1,13 @@ use alloc::boxed::Box; use alloc::vec::Vec; +use core::iter::Cloned; use core::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; use core::ops::{Deref, DerefMut, Index, IndexMut}; use core::slice::Iter; -use num_traits::Zero; use crate::layout::rank::DynRank; use crate::layout::ranked::Ranked; -use crate::layout::shape::{IntoShape, Shape}; +use crate::layout::shape::Shape; use crate::Axis; const CAP: usize = 4; @@ -19,19 +19,9 @@ pub enum DynAxesRepr Alloc(Box<[T]>), } +/// An array shape with a dynamic rank. pub type DShape = DynAxesRepr; -impl DynAxesRepr -{ - fn ndim(&self) -> usize - { - match self { - DynAxesRepr::Inline(len, _) => *len, - DynAxesRepr::Alloc(items) => items.len(), - } - } -} - impl Deref for DynAxesRepr { type Target = [T]; @@ -52,7 +42,13 @@ impl DerefMut for DynAxesRepr { fn deref_mut(&mut self) -> &mut Self::Target { - todo!() + match self { + DynAxesRepr::Inline(len, arr) => { + debug_assert!(*len <= arr.len()); + unsafe { arr.get_unchecked_mut(..*len) } + } + DynAxesRepr::Alloc(items) => items, + } } } @@ -92,33 +88,6 @@ impl IndexMut for DynAxesRepr } } -impl Default for DynAxesRepr -where T: Zero + Copy -{ - fn default() -> Self - { - Self::Inline(1, [T::zero(); 4]) - } -} - -impl Zero for DShape -{ - fn zero() -> Self - { - Self::Inline(1, [0usize; CAP]) - } - - fn is_zero(&self) -> bool - { - self.iter().all(|e| e.is_zero()) - } - - fn set_zero(&mut self) - { - self.iter_mut().for_each(|e| e.set_zero()); - } -} - impl From for DynAxesRepr where Rhs: AsRef<[T]>, @@ -156,7 +125,7 @@ macro_rules! impl_op { /// *Panics* if the two dimensionalities are different impl $op_trait for DShape where - Rhs: IntoShape, + Rhs: Into, { type Output = DShape; @@ -170,10 +139,10 @@ macro_rules! impl_op { /// *Panics* if the two dimensionalities are different impl $op_assign_trait for DShape where - Rhs: IntoShape, + Rhs: Into, { fn $op_assign_fn(&mut self, rhs: Rhs) { - let other = rhs.into_shape(); + let other = rhs.into(); for i in 0..self.ndim().max(other.ndim()) { self[i].$op_assign_fn(other[i]); } @@ -204,11 +173,16 @@ where T: Clone impl Shape for DynAxesRepr { type Iter<'a> - = Iter<'a, usize> + = Cloned> where Self: 'a; + fn axis_len(&self, axis: usize) -> usize + { + self[axis] + } + fn iter(&self) -> Self::Iter<'_> { - (**self).iter() + (**self).iter().cloned() } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index df3ef6b9e..fa2358873 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -13,6 +13,7 @@ pub mod rank; pub mod ranked; mod shape; mod n_repr; +mod dyn_repr; use core::any::type_name; use core::error::Error; @@ -23,6 +24,7 @@ use crate::layout::ranked::Ranked; #[allow(deprecated)] pub use bitset::{Layout, LayoutBitset}; +pub use dyn_repr::DShape; pub use n_repr::NShape; pub use shape::Shape; diff --git a/src/layout/n_repr.rs b/src/layout/n_repr.rs index 2d9afaaf5..a29d932bf 100644 --- a/src/layout/n_repr.rs +++ b/src/layout/n_repr.rs @@ -171,6 +171,19 @@ where ConstRank: Rank } } +impl Zero for NShape +{ + fn zero() -> Self + { + Self([usize::zero(); N]) + } + + fn is_zero(&self) -> bool + { + self.0.iter().all(|e| *e == 0usize) + } +} + macro_rules! impl_op { ($op_trait:ty, $op_fn:ident, $op_assign_trait:ty, $op_assign_fn:ident) => { impl $op_trait for NShape @@ -270,6 +283,6 @@ impl Default for NShape { fn default() -> Self { - Self([0; N]) + Self::zero() } } From 1918542ab79f153944542710eb5112050db3a2d8 Mon Sep 17 00:00:00 2001 From: Adam Kern Date: Sun, 22 Feb 2026 15:03:50 -0500 Subject: [PATCH 6/6] Add documentation to shape trait --- src/layout/shape.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/layout/shape.rs b/src/layout/shape.rs index b16398d63..0207549e3 100644 --- a/src/layout/shape.rs +++ b/src/layout/shape.rs @@ -2,7 +2,25 @@ use core::iter::Map; use crate::layout::ranked::Ranked; -/// A trait for array shapes. +/// A trait for array shapes: lists of `usize` describing the length of each dimension of an array. +/// +/// ## Size Limits +/// Arrays cannot have more than [`usize::MAX`] elements; otherwise, it would not be possible +/// to address all of the elements in-memory. In addition, since an array may have negative strides +/// (i.e., elements _behind_ the current pointer in memory), arrays must be addressable using +/// [`isize`]. So no array can have more than [`isize::MAX`] elements. Finally, for multi-byte +/// element types, the total byte size of the array also cannot exceed `isize::MAX`. +/// +/// Implementing `Shape` for a type does not guarantee that the type will always represent an +/// array that adheres to these size limits. It does, however, provide access to two methods that +/// can cheaply check these invariants: [`Shape::size_checked`] and [`Shape::size_bytes_checked`]. +/// In order for an instance of a `Shape` to be valid, both of these methods must return `Some(_)`. +/// +/// ## Mutability +/// The `Shape` trait does not provide any sort of mutability for the lengths of each axis. +/// This allows users to define constant-sized shapes, which can significantly increase performance. +/// +/// Since `Shape` is still experimental, the mechanisms for mutability are still being designed. pub trait Shape: Ranked { /// The iterator type over the dimensions of the shape. @@ -27,7 +45,6 @@ pub trait Shape: Ranked /// Get the number of bytes that this array would fill. /// - /// # Safety /// This method checks for bytes overflow past `isize`. If this method returns `Some(_)`, /// then users know that an allocated array is indexable using `isize` offsets. fn size_bytes_checked(&self) -> Option