My issue is essentially this: I have a type I would like to instantiate at some point with a validity check in an inner constructor, but before that I need to collect the data needed to actually build the type. I figured using the builder pattern commonly used by Rust packages might do the trick, even if it wasn’t the most efficient method ever.
The (rather massive) type that I am trying to build is defined as follows:
"""
The result of building an exercise.
"""
struct Tehtävä{
    Nimi         <: AbstractString,
    Tehtävänanto <: AbstractString,
    Ratkaisu     <: Union{Missing, <:AbstractString},
    Vastaus      <: Union{Missing, <:AbstractString},
    Avainsanat   <: Vector{<:AbstractString},
    Assignment   <: AbstractString,
    Solution     <: Union{Missing, <:AbstractString},
    Answer       <: Union{Missing, <:AbstractString},
    Keywords     <: Vector{<:AbstractString},
} <: AbstraktiTehtävä
    nimi            ::Nimi
    tehtävänanto    ::Tehtävänanto
    ratkaisu        ::Ratkaisu
    vastaus         ::Vastaus
    avainsanat      ::Avainsanat
    assignment      ::Assignment
    solution        ::Solution
    answer          ::Answer
    keywords        ::Keywords
    function Tehtävä{
        Nimi        ,
        Tehtävänanto,
        Ratkaisu    ,
        Vastaus     ,
        Avainsanat  ,
        Assignment  ,
        Solution    ,
        Answer      ,
        Keywords    ,
    }(
        nimi         ::Nimi        ,
        tehtävänanto ::Tehtävänanto,
        ratkaisu     ::Ratkaisu    ,
        vastaus      ::Vastaus     ,
        avainsanat   ::Avainsanat  ,
        assignment   ::Assignment  ,
        solution     ::Solution    ,
        answer       ::Answer      ,
        keywords     ::Keywords    ,
    ) where {
        Nimi         <: AbstractString,
        Tehtävänanto <: AbstractString,
        Ratkaisu     <: Union{Missing, <:AbstractString},
        Vastaus      <: Union{Missing, <:AbstractString},
        Avainsanat   <: Vector{<:AbstractString},
        Assignment   <: AbstractString,
        Solution     <: Union{Missing, <:AbstractString},
        Answer       <: Union{Missing, <:AbstractString},
        Keywords     <: Vector{<:AbstractString},
    }
        if isempty(nimi)
            error("Tehtävältä puuttuu nimi…")
        end
        if isempty(tehtävänanto) && isempty(assignment)
            error("Tehtävän $nimi tehtävänanto puuttuu…")
        end
        if isempty(avainsanat) && isempty(keywords)
            error("Tehtävän $nimi avainsanat puuttuvat…")
        else
            if true ∈ isempty.((sana for sana ∈ avainsanat))
                error("Jokin tehtävän $nimi avainsanoista oli tyhjä…")
            end
            if true ∈ isempty.((sana for sana ∈ keywords))
                error("Jokin tehtävän $nimi avainsanoista oli tyhjä…")
            end
        end
        new(
            nimi         ,
            tehtävänanto ,
            ratkaisu     ,
            vastaus      ,
            avainsanat   ,
            assignment   ,
            solution     ,
            answer       ,
            keywords     ,
        )
    end
    """
    Generates this type from a give type builder.
    """
    function Tehtävä(a::TehtävänArgumentit)
        Tehtävä(
            a.nimi,
            a.tehtävänanto, a.ratkaisu, a.vastaus, a.avainsanat,
            a.assignment, a.solution, a.answer, a.keywords
        )
    end
end
The builder is defined very similarly, except it does not take other builders as arguments:
"""
A builder used to generate the fields of an exercise during parsing.
"""
struct TehtävänArgumentit{
    Nimi         <: AbstractString,
    Tehtävänanto <: AbstractString,
    Ratkaisu     <: Union{Missing, <:AbstractString},
    Vastaus      <: Union{Missing, <:AbstractString},
    Avainsanat   <: Vector{<:AbstractString},
    Assignment   <: AbstractString,
    Solution     <: Union{Missing, <:AbstractString},
    Answer       <: Union{Missing, <:AbstractString},
    Keywords     <: Vector{<:AbstractString},
}
    nimi         ::Nimi
    tehtävänanto ::Tehtävänanto
    ratkaisu     ::Ratkaisu
    vastaus      ::Vastaus
    avainsanat   ::Avainsanat
    assignment   ::Assignment
    solution     ::Solution
    answer       ::Answer
    keywords     ::Keywords
    function TehtävänArgumentit{
        Nimi         ,
        Tehtävänanto ,
        Ratkaisu     ,
        Vastaus      ,
        Avainsanat   ,
        Assignment   ,
        Solution     ,
        Answer       ,
        Keywords     ,
    }(
        nimi         ::Nimi         ,
        tehtävänanto ::Tehtävänanto ,
        ratkaisu     ::Ratkaisu     ,
        vastaus      ::Vastaus      ,
        avainsanat   ::Avainsanat   ,
        assignment   ::Assignment   ,
        solution     ::Solution     ,
        answer       ::Answer       ,
        keywords     ::Keywords     ,
    ) where {
        Nimi         <: AbstractString,
        Tehtävänanto <: AbstractString,
        Ratkaisu     <: Union{Missing, <:AbstractString},
        Vastaus      <: Union{Missing, <:AbstractString},
        Avainsanat   <: Vector{<:AbstractString},
        Assignment   <: AbstractString,
        Solution     <: Union{Missing, <:AbstractString},
        Answer       <: Union{Missing, <:AbstractString},
        Keywords     <: Vector{<:AbstractString},
    }
        new(
            nimi,
            tehtävänanto, ratkaisu, vastaus, avainsanat,
            assignment, solution, answer, keywords
        )
    end
end
The reason both of these are parametric is that their parameters might vary from Strings to SubStrings. Now my issue is that I can’t seem to be able to build an instance of the builder type TehtävänArgumentit. Trying to generate an initial value of TehtävänArgumentit with the call
tehtävän_argumentit = TehtävänArgumentit(
        basename(tiedoston_nimi),
        "",
        "",
        "",
        String[],
        "",
        "",
        "",
        String[]
    )
results in an error
ERROR: LoadError: MethodError: no method matching TehtävänArgumentit(::String, ::String, ::String, ::String, ::Vector{String}, ::String, ::String, ::String, ::Vector{String})
I thought that the UnionAll types in the type specification of TehtävänArgumentit and Tehtävä would handle all of the possible sub-type combinations, but this is obviously not the case here. How might I change this to allow the builder to be instantiated while still allowing the types of its fields to vary during construction, as I return new builders from functions f: (builder, new_field_value) ↦ builder_with_modified_field ?