We currently assume that axes(A::AbstractArray, i) <: AbstractUnitRange in lots of code (so even tighter than your range assumption), and we also expect axes to have integer elements. I think you’re safe to assume the same. A custom “array” that breaks this will already be in lots of deep water and I’d say they’re fine swimming out there on their own.
I also asked myself from time to time what should be assumed about AbstractArray indices. I guess its a common question, when writing generic code. So I think it would be good to document this.
After a close reading of the Interfaces and Arrays with custom indices documentation, I saw that both are pretty specific about axes returning (tuples of) AbstractUnitRange.
I am under the impression that this allows non-integer types though: anything goes that has a oneunit, eg I could define
struct RealUnitRange{T <: Real} <: AbstractUnitRange{T}
start::T
stop::T
len::Int
end
RealUnitRange(start, length::Integer) = RealUnitRange(start, start + length, length)
function Base.getindex(rur::RealUnitRange, i::Int)
@boundscheck @assert 1 ≤ i ≤ rur.len
rur.start + i
end
# ...
and it would be valid for axis to return this. Or the same exercise with, for example, Dates.Date.
This means that I cannot rely on arithmetic with integers; I should at least increment with multiples of oneunit. Also, in theory, I could be worried about floating point error and similar, since the eltype of the range could be anything.
Interesting — you might be right. It’s not something that I’ve personally seen or tried, so I wouldn’t be surprised if such an array would fail in some situations.
One thing we do assume is that A[axes(A)…] == A. This means that it also needs to be a valid index type. We also sometimes grab axes from one array and use them to index into another — so it needs to be a valid index type for all array types. It may be possible to extend to_index to make that work for a non-integer, but I’d still be surprised if everything just worked.