Reinterpret Tuple as Struct, and vector thereof

So I am playing a bit with the Clipper.jl library, which uses a type IntPoint defined as a struct of two Int64 fields. However, it would be more convenient for me to use static vectors of fixed-point reals, i.e. SVector{2,Fixed{64,F}} where{F} (let’s call this type T); namely, T will support linear algebra, conversion to/from floats, et.c

So I am trying to convert data of type Vector{T} to Vector{IntPoint}, for passing to the Clipper.jl functions (which is a set of wrappers to a C library), without copying the objects (The fact that the IntPoint data are used in ccall, and that SVector holds its data in a tuple, guarantees that casting pointers around is safe). For a single T object I can do this in the following way:

Base.reinterpret(::Type{Clipper.IntPoint}, pt::SVector{2,Fixed{Int64}}) =
  unsafe_load(reinterpret(Ptr{Clipper.IntPoint}, pointer_from_objref(Ref(pt))))

(yes I used to program in C, but a pointer cast seems appropriate here since we are passing this to ccall anyway).

However I am unable to find a way to do this with a Vector{T} instead; my obvious attempt (inserting Vector{} everywhere in the above example) failed with "pointerref: invalid pointer type" (which, I imagine, is due to the difference between a C array and a Julia vector), while calling vec2 = reinterpret(IntPoint, vec1) does return a Vector{IntPoint}, but seems to make a copy (I checked that pointer_from_objref(Ref(vec2[1])) is different from pointer_from_objref(Ref(vec1[1]))).

What would be the best way to perform such an in-place reinterpretation?

I will not answer directly your question, but maybe provide some useful information. Although the mutations of the elements of an array of immutable structs change the pointers, that happens in the stack, thus this is very fast and does not “allocate”. Thus, if you vector is of elements of one or other type, the elements can be mutated without allocating:

julia> struct IntPoint
         x :: Int64
         y :: Int64
       end

julia> v = Union{IntPoint,SVector{2,Int64}}[ IntPoint(1,1) ]
1-element Array{Union{IntPoint, SArray{Tuple{2},Int64,1,2}},1}:
 IntPoint(1, 1)

julia> function mutate!(v)
          v[1] = IntPoint(2,2)
          nothing
       end
mutate! (generic function with 1 method)

julia> @btime mutate!(v)
  10.767 ns (0 allocations: 0 bytes)

julia> function mutate2!(v)
          v[1] = SVector{2,Int64}(3,3)
          nothing
       end
mutate2! (generic function with 1 method)

julia> @btime mutate2!(v)
  11.043 ns (0 allocations: 0 bytes)

julia>

This works, for example:

julia> a = rand(SVector{2,Int}, 10);

julia> b = reinterpret(IntPoint, a);

and no messing around with unsafe pointer operations.

The result is a Base.ReinterpretArray{IntPoint, 1}, which is a subtype of AbstractVector{IntPoint}. Unfortunately, the Clipper.jl library currently only supports Vector. One way around this would be to do:

julia> v = unsafe_wrap(Array, pointer(b), length(b))

to get a v of type Vector{IntPoint}. Be sure to wrap GC.@protect b begin ... end around any code that needs to use v, to ensure that the underlying array doesn’t get garbage-collected while you are using it.

4 Likes