# Type Constructors (=multiple dispatch for \`UnionAll\`)

**URL:** https://discourse.julialang.org/t/type-constructors-multiple-dispatch-for-unionall/139622
**Category:** Internals & Design
**Created:** [September 23, 2026, 7:45am UTC](https://discourse.julialang.org/t/type-constructors-multiple-dispatch-for-unionall/139622 "2026-09-23T07:45:59Z")
**Posts on this page:** 1
**Page:** 1

<div class="post-metadata">

### Author: ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)
#### Post date: [September 23, 2026, 7:45am UTC](https://discourse.julialang.org/t/type-constructors-multiple-dispatch-for-unionall/139622/1 "2026-09-23T07:45:59Z")

</div>

## TL;DR

Let’s use (static) multiple dispatch for `UnionAll` type application!  
This would introduce type constructors. They would take a `UnionAll` to construct a type similar to how inner (object/value) constructors take a type to construct an object/value.

## Motivation

### Backwards Compatibility

Suppose we wanted to generalize `SubString` from

```julia
struct SubString{T <: AbstractString} <: AbstractString
    string::T
    offset::Int
    ncodeunits::Int
end

```

into:

```julia
struct SubString{T <: AbstractString, R <: AbstractUnitRange{<:Integer}} <: AbstractString
    string::T
    ind::R
end

```

This would have multiple advantages (allowing smaller index types, allow something like `Base.OneTo`, generally reusing code) which are not relevant for our discussion here.

Object constructors and properties could preserve much of the existing interface. However, `SubString{String}` would become a `UnionAll`, so existing fields declared as `::SubString{String}` would no longer be concrete. For all I know, this incompatibility could not be avoided (withing the desired solution space, i.e without introducing additional types or something) with Julias features as of today.

### Constraining Types

Currently, it’s often not possible to usefully constraint types so that invalid types cannot even be constructed (used). Instead, constructors need to make sure that they don’t construct an object of an (then already existing) invalid type.

So assume we wanted to define for our example above (to not introduce a more useful, but another example)

```julia
SubString{UnitRange{Int}}(bytes::Vector{UInt8}) =
    SubString{String, UnitRange{Int}}(String(bytes), 1:length(bytes))

```

then this could not be constructed, as `SubString` is a `UnionAll`, which only accepts a `T <: AbstractString` as first `TypeVar`, not an `AbstractUnitRange`.  
The only possibility to make this work is to remove the type constraint(s) from the type definition and let the constructor do this work. While that kind of works, it not only feels wrong, but it becomes obvious, that regular constructors are currently more powerful.

### Asymmetry

There is also an inherent asymmetry involved in defining parametric types with more than one type parameter, closely related the the point mentioned before: `SubString{T <: AbstractString}` automatically is a `UnionAll`, while `SubString{R <: AbstractUnitRange{<:Integer}}` isn’t, although they are clearly distinguishable. Again, regular constructors, using multiple dispatch, are just more powerful due to their multiple dispatch.

### Conclusion

Julia lets us customize object construction through constructor methods, but type application such as `Foo{T}` always follows positional parameter substitution.  
If we only had multiple (static) dispatch for type construction! So could we support **inner type constructors** , analogous to inner object constructors?

## Proposed Syntax

Inside the type definition, allow (type) constructors without the parenthesized object argument list:

```julia
struct SubString{T <: AbstractString, R <: AbstractUnitRange{<:Integer}} <: AbstractString
    string::T
    ind::R

    SubString{T <: AbstractString} = new{T, UnitRange{Int}}
    SubString{R <: AbstractUnitRange{<:Integer}} =
        new{T,R} where T <: AbstractString
    SubString{T,R} = new{T,R}
end

```

The distinction would be:

- `new{T,R}` returns the canonical type through native parameter substitution.
- `new{T,R}(string, ind)` constructs an instance, as today.

From the user’s perspective:

```julia
SubString{String}
# Concrete type: String parent, UnitRange{Int} indices

SubString{String, UnitRange{Int32}}
# Concrete type: String parent, UnitRange{Int32} indices

SubString{UnitRange{Int}}
# UnionAll: any permitted string type, UnitRange{Int} indices
# Equivalent to:
SubString{T, UnitRange{Int}} where T <: AbstractString

SubString{String, R} where R <: AbstractUnitRange{<:Integer}
# UnionAll: String parent, any permitted range representation

```

`SubString` would still denote the generic `UnionAll`.

## Semantics

Type constructors would dispatch on parameter combinations and bounds. Their bodies could use `= begin … end` with Julia code to validate parameters, supply defaults, or reorder them before returning a type.

Like inner object constructors, defining an inner type constructor would suppress automatic public type-application defaults. Explicit forwarding, as above, would restore the desired forms. Types without custom definitions would behave exactly as today.

So this should be fully backwards-compatible with the exception of local type aliases in the constructor which could have the same syntax. I have never seen them, but if they exist in larger frequency, we could either require, at least initially, a `new` (but this would disallow type constructor delegates) or change the syntax. Although I think that the proposed syntax is the logical choice, we could introduce a block-form with some keyword needing an `end`.

Arguments would be type objects, permitted value parameters, or `TypeVar`s, so everything would need to be “static”. Ambiguous applications would be errors. At least initially constructors would be fixed with the type definition, so their would be no external type constructors. Type constructors must return specializations of that same nominal type (so a `SubString` type constructor must return a concrete or `UnionAll` `SubString`, not a `Vector`).

## Summary

The goal is to make type application customizable in the same spirit as object construction, allowing a type’s representation to evolve while preserving useful existing spellings such as `SubString{String}`. This should solve all motivating problems and could make the language more consistent by allowing the same (and defining) language mechanism (multiple dispatch) to be applied not only on functions and on objects (=callables), but also on types.

What do you think? Obviously, a lot of details would need to be clarified before/while implementing it, but does the general idea sound interesting?
