Plan for dimension types

Open
#519 7 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
20/100
Issue type
Refactor
Clarity
Needs clarification
Activity status
Stale
Tech stack
rust
Domain
data

Research direction

Start by reading the existing Dimension and ArrayBase implementations, then compare how Ix2 and IxDyn currently represent shapes and strides. The proposal requires agreement on the associated-type design, conversion traits, and type-level operations before implementation; completion would include resolving the related dimension issues and validating the affected API.

Written by the indexing model from the issue text.

Description

I've been thinking about the dimension types for a while. Two things I don't like about the current implementation are that:

  1. Strides are represented as usize and have to be converted to isize every time they're used. This is error-prone and confusing.
  2. It seems a little weird that the dimension type itself (instead of an associated type) has a representation. For example, "2-D" doesn't necessary imply a specific representation to me.

What I'd like to do is something like the following:

pub trait Dimension
where
    for<'a> Into<Self::OwnedUsize> for &'a Self::BorrowedUsize,
    for<'a> Into<Self::OwnedIsize> for &'a Self::BorrowedIsize,
{
    type OwnedUsize: AsRef<Self::BorrowedUsize> + AsMut<Self::BorrowedUsize> + AsRef<[usize]> + AsMut<[usize]>;
    type BorrowedUsize: ?Sized + AsRef<[usize]> + AsMut<[usize]>;
    type OwnedIsize: AsRef<Self::BorrowedIsize> + AsMut<Self::BorrowedIsize> + AsRef<[isize]> + AsMut<[isize]>;
    type BorrowedIsize: ?Sized + AsRef<[isize]> + AsMut<[isize]>;
}

pub struct Ix2;
pub struct IxDyn;

impl Dimension for Ix2 {
    type OwnedUsize = [usize; 2];
    type BorrowedUsize = [usize; 2];
    type OwnedIsize = [isize; 2];
    type BorrowedIsize = [isize; 2];
}

impl Dimension for IxDyn {
    type OwnedUsize = IxDynImpl<usize>;
    type BorrowedUsize = [usize];
    type OwnedIsize = IxDynImpl<isize>;
    type BorrowedIsize = [isize];
}

pub trait IntoDimOwnedUsize {
    type Dim: Dimension;
    fn into_dim_owned_usize(self) -> Self::Dim::OwnedUsize;
}

pub trait AsDimBorrowedUsize {
    type Dim: Dimension;
    fn as_dim_borrowed_usize(&self) -> &Self::Dim::BorrowedUsize;
}

pub trait IntoDimOwnedIsize { ... }

pub trait AsDimBorrowedIsize { ... }

pub struct ArrayBase<S, D>
where
    S: Data,
{
    data: S,
    ptr: *mut S::Elem,
    dim: D::OwnedUsize,
    strides: D::OwnedIsize,
}

impl<A, S, D> ArrayBase<S, D>
where
    S: Data<Elem = A>,
    D: Dimension,
{
    pub fn shape(&self) -> &D::BorrowedUsize {
        // ...
    }

    pub fn strides(&self) -> &D::BorrowedIsize {
        // ...
    }
}

Once Rust has generic associated types, we can simplify this to:

pub trait Dimension
where
    for<'a, T: Clone> Into<Self::Owned<T>> for &'a Self::Borrowed,
{
    type Owned<T>: AsRef<Self::Borrowed<T>> + AsMut<Self::Borrowed<T>> + AsRef<[T]> + AsMut<[T]>;
    type Borrowed<T>>: ?Sized + AsRef<[T]> + AsMut<[T]>;
}

pub struct Ix2;
pub struct IxDyn;

impl Dimension for Ix2 {
    type Owned<T> = [T; 2];
    type Borrowed<T> = [T; 2];
}

impl Dimension for IxDyn {
    type Owned<T> = IxDynImpl<T>;
    type Borrowed<T> = [T];
}

pub trait IntoDimOwned<T> {
    type Dim: Dimension;
    fn into_dim_owned(self) -> Self::Dim::Owned<T>;
}

pub trait AsDimBorrowed<T> {
    type Dim: Dimension;
    fn as_dim_borrowed(&self) -> &Self::Dim::Borrowed<T>;
}

pub struct ArrayBase<S, D>
where
    S: Data,
{
    data: S,
    ptr: *mut S::Elem,
    dim: D::Owned<usize>,
    strides: D::Owned<isize>,
}

impl<A, S, D> ArrayBase<S, D>
where
    S: Data<Elem = A>,
    D: Dimension,
{
    pub fn shape(&self) -> &D::Borrowed<usize> {
        // ...
    }

    pub fn strides(&self) -> &D::Borrowed<isize> {
        // ...
    }
}

I'd also add various type-level arithmetic operations on the dimension types, which are necessary for things like co-broadcasting (trait PartialOrdDim) and fold_axes (trait SubDim).

We can also add Shape<T>, Strides<T>, and Index<T> thin wrapper types.

This approach would resolve things like #489 and this comment on #367.

Thoughts?

Dominant language
Rust
Stars
4.3k
Forks
391
PR merge metrics
No merged PRs in 30d

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from rust-ndarray/ndarray

All issues in rust-ndarray/ndarray

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.