Buggy array of array



I’ve spent many hours trying to figure out why this code misbehaves. First, let’s explain this code (which is an excerpt of my real code, just enough to reproduce the issue).
I’ve a custom type named MyScalar, which is basically an array with shifted indexes, and a type MyVector, meant to be a standard (i.e. no index shifting) array of MyScalar elements.
However, when I try to run it (code below), the output becomes buggy and keeps printing “This is my type” after any other command I execute. Actually, twice per command (because 2 is the length of the MyScalar).
I truly can’t understand what’s happening here!

module MyModule

using Compat, OffsetArrays

export MyScalar, MyVector

struct MyScalar{T} <: AbstractArray{T,1}

function my_show(io::IO, MS::MyScalar)
println(“This is my type!\n”)

MyScalar{T}(::Type{T}, n::Int, off::Int = 0) = MyScalar(OffsetArray{T}(undef, off:(off+n-1)), n)
MyScalar(n::Int) = MyScalar(Float64, n)
MyScalar{T}(::Type{T}, oa::OffsetArray{T}) = MyScalar(oa, 1 + indices(oa)[1].stop - indices(oa)[1].start)
Base.size(MS::MyScalar) = (MS.dim,)
Base.IndexStyle(::Type{<:MyScalar}) = IndexLinear()
Base.similar(MS::MyScalar, ::Type{T}, d::Dims) where {T} = MyScalar(T, d[1])
Base.getindex(MS::MyScalar, i::Int) = MS.data[i]
Base.setindex!(MS::MyScalar, v, i::Int) = (MS.data[i] = v)
Base.show(io::IO, MS::MyScalar) = my_show(io, MS)
Base.indices(MS::MyScalar) = indices(MS.data)

struct MyVector <: AbstractArray{MyScalar{Int32}, 1}

MyVector() = MyVector(Array{MyScalar{Int32},1}(3),3) #arbitrary fixed length
Base.size(MV::MyVector) = (MV.dim,)
Base.getindex(MV::MyVector, i::Int) = MV.data[i]
Base.setindex!(MV::MyVector, v, i::Int) = (MV.data[i] = v)

Example code:

using MyModule
myv = MyVector()
myv[2] = MyScalar(Int32, 2)

function my_show(io::IO, MS::MyScalar)
    println(io, “This is my type!\n”) # note io


Wow! Can’t believe how much time I wasted looking elsewhere, when the issue was that simple!
Now I’m just curious to understand what happened when I didn’t specify the “io”. The documentation of print explicitly says:

Write to io (or to the default output stream STDOUT if io is not given)

So, why that odd behaviour?


It’s easy (and common!) to swap out the default stream for something else like an IOBuffer to create a string. Functions like sprint(show, MyScalar(...)) will do it for you! Of course, they’ll be buggy since they won’t receive any data written to their IO object, and instead you’ll get spurious output at stdout.