Defining custom subtype of AbstractArray

I am having some trouble defining my struct as a subtype of AbstractArray.

As an example, say I want to define a Point in dim dimensional space as follows:

struct Point{dim} <: AbstractArray{Float64,dim}
	x::NTuple{dim, Float64}
end

Now according to the documentation, the minimum functions I need to overload for Point to be an AbstractArray are: Base.size, Base.IndexStyle, Base.getindex and Base.setindex!

So I do it as follows:

Base.size(p::Point{dim}) where dim = (dim,)
Base.IndexStyle(::Type{<:Point}) = IndexLinear()
Base.getindex(p::Point{dim}, i::Int64) where dim = p.x[i]
Base.setindex!(p::Point{dim}, v, i::Int64) where dim = (p.x[i] = v)

If I try to initialize this struct, I get an error message that I can’t quite understand:

julia> Point((1.5,2.4))
2-element Point{2}:Error showing value of type Point{2}:
ERROR: BoundsError: attempt to access (Base.OneTo(2),)
  at index [2]
Stacktrace:
 [1] getindex(::Tuple{Base.OneTo{Int64}}, ::Int64) at ./tuple.jl:24
 [2] axes at ./abstractarray.jl:57 [inlined]
...

I would greatly appreciate inputs on the correct way to do this.

Thanks!

Your trouble is stemming from the fact that you’re reporting to AbstractArray that your struct has dim dimensions when it’s only 1-dimensional. Change it to struct Point{dim} <: AbstractArray{Float64,1} and I think all should work. I’d probably rename dim to len to make it clearer.

Oh, not quite all will work: Your setindex! method will run into trouble since tuples are immutable, so if you use a tuple as your backing you need to make the strict itself mutable and replace the entire tuple when you change it.

Also for generality you could do:

struct Point{T,dim} <: AbstractArray{T,1}
    x::NTuple{dim, T}
end

So its not limited to Float64

You’ve actually re-created the bug in the original post. You want <: AbstractArray{T, 1}, not <: AbstractArray{T, dim}.

Oops I thought that looked weird! mbaumans code change is kinda hard to read in inline quotes :wink:

If you make it mutable Point{dim, T} <: AbstractVector{T} (here AbstractVector{T} is shorthand for AbstractArray{T, 1}), then consider

@inline Base.setindex!(p::Point{dim, T}, v, i::Int64) where {dim, T}
    @boundscheck checkbounds(p, i)
    if isbitstype(T)
        GC.@preserve p Base.unsafe_store!(convert(Ptr{T}, pointer_from_objref(p)), convert(T, v), i)
        return v
    else
        vv = convert(T, v)
        p.x = ntuple( (n-> (n===i ? vv : @inbounds p.x[n]) ), dim)
        return v
    end
end

This is what StaticArrays does for MVector.