Outer constructor results in StackOverflowError


#1

I’m trying to add an outer constructor for a parametric type, so that the object is only returned if it passes some tests. Here’s a MRE of what I’m trying to do:

abstract type MyAbstractType end

mutable struct MyType{ST,LT<:AbstractFloat} <: MyAbstractType
    a::Set{ST}
    b::Set{ST}
    c::Set{Tuple{ST,ST,LT}}
end

function MyType{ST,LT<:AbstractFloat}(a::Set{ST}, b::Set{ST}, c::Set{Tuple{ST,ST,LT}})
    if !all(0.0 .<= [x[3] for x in c] .<= 1.0)
        error("NOPE")
    end
    MyType(a, b, c)
end

i = Set([1, 2]) 
j = Set([3, 4])
k1 = Set([(1, 3, 0.2),(2, 4, 0.5)]) # Correct
k2 = Set([(1, 3, 0.2),(2, 4, 1.4)]) # Incorrect

MyType(i, j, k2) # Working - returns an error
MyType(i, j, k1) # Not working - StackOverflow

This results in a StackOverflowError, because the constructor calls itself (unless there is an error because the conditions are not satisfied).

I’m quite frankly lost about what is wrong with this code. Any tips?


#2

Use an inner constructor:

mutable struct MyType{ST,LT<:AbstractFloat} <: MyAbstractType
    a::Set{ST}
    b::Set{ST}
    c::Set{Tuple{ST,ST,LT}}
    function MyType{ST,LT<:AbstractFloat}(a::Set{ST}, b::Set{ST}, c::Set{Tuple{ST,ST,LT}})
        if !all(0.0 .<= [x[3] for x in c] .<= 1.0)
            error("NOPE")
        end
        new{ST, LT}(a, b, c)
    end
end

#3

Instead do MyType{eltype(a),...}(a, b, c)


#5

thanks!


#6

If you want your struct to be created only if it pass the test you should use an inner constructor, as at-kristoffer.carlsson suggested. This way the test can never be by-passed. The outer constructor test can be by-passed if you call MyType{eltype(a),...}(a,b,c) directly.