Skip to content

fix: make array type aliases covariant when used as struct fields - #1628

Open
BenjaminLucier wants to merge 1 commit into
rust-ndarray:masterfrom
BenjaminLucier:fix/alias-variance-in-structs
Open

BenjaminLucier wants to merge 1 commit into
rust-ndarray:masterfrom
BenjaminLucier:fix/alias-variance-in-structs

Conversation

@BenjaminLucier

Copy link
Copy Markdown

Fixes #1610.

Bug

Since #1480, a bare ArrayView or CowArray in a function signature shortens fine, but a struct containing one of the type aliases is still invariant, over both the view lifetime and the element type:

use ndarray::{Array1, CowArray, Ix1};

struct Holder<'a>(CowArray<'a, f64, Ix1>);
fn shorten<'a: 'b, 'b>(h: Holder<'a>) -> Holder<'b> { h } // error: lifetime may not live long enough

struct Test<T>(Array1<T>);
fn shorten_elem<'a: 'b, 'b>(t: Test<&'a u8>) -> Test<&'b u8> { t } // same error

This affects Array, ArcArray, CowArray, ArrayView, ArrayViewMut and RawArrayView, and every dimension alias built on them, on 0.17.0 through master. It is also the original use case of #1332, whose reporter hit the problem with a view "deep inside a data structure".

Root cause

#1480 made ArrayBase itself covariant by adding a defaulted third parameter, ArrayBase<S, D, A = <S as RawData>::Elem>, but the public aliases kept the two-parameter form, so they still expand to a type containing the projection <S as RawData>::Elem.

rustc computes a struct's variance from its field types without normalizing projections, and treats every parameter inside a projection as invariant (rust-lang/rust#115799). In a function signature the alias is normalized before the subtyping check, which is why #1480's test passes while a struct field stays invariant.

Fix

Spell out the element type in the seven base aliases:

-pub type CowArray<'a, A, D> = ArrayBase<CowRepr<'a, A>, D>;
+pub type CowArray<'a, A, D> = ArrayBase<CowRepr<'a, A>, D, A>;

The bound S: RawData<Elem = A> forces A == S::Elem, so each alias names exactly the same type as before — not a breaking change, suitable for a patch release.

A struct field written as ArrayBase<S, D> directly still gets the default and stays invariant; ndarray can't fix that without removing the default (breaking). The ArrayBase docs now explain the third parameter, with a doc-tested example of a covariant generic wrapper struct.

Tests

tests/variance.rs gains a struct-wrapped shorten_* case per alias, which compiles only if the struct is covariant, plus a runtime test calling them. RawArrayViewMut is omitted because, like *mut T, it is correctly invariant. Without the alias change all seven new cases fail to compile; with it they pass.

Checked locally on stable 1.99.1 (aarch64-apple-darwin) with RUSTFLAGS=-D warnings: clippy (-F approx,serde,rayon), the scripts/all-tests.sh steps except BLAS integration and benches (no features / approx / all features + quickcheck / release / --features blas lib / blas-mock / examples / doc tests), and RUSTDOCFLAGS=-Dwarnings cargo doc --no-deps --all-features. Not run locally: nightly cargo fmt, MSRV 1.87, miri, nostd, cross, cargo-careful, BLAS integration.

AI assisted.

rust-ndarray#1480 made `ArrayBase` covariant by adding a defaulted third parameter
`A = <S as RawData>::Elem`, but the public type aliases still used the
default. rustc computes a struct's variance from its field types without
normalizing projections (rust-lang/rust#115799), so any struct holding an
`ArrayView`, `ArrayViewMut`, `CowArray`, `Array`, `ArcArray` or
`RawArrayView` was invariant over the view lifetime and the element type.

Spell out the element type in the seven base aliases. Because of the
bound `S: RawData<Elem = A>`, each alias names the same type as before,
so this is not a breaking change.

Also document the third parameter on `ArrayBase`, and add struct-wrapped
regression tests for each alias to tests/variance.rs.

Fixes rust-ndarray#1610

AI assisted

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Type covariance absent when used in struct

1 participant