# Constructors for parametric types

**URL:** <https://discourse.julialang.org/t/constructors-for-parametric-types/119971>\
**Category:** General Usage\
**Tags:** parametric-types, constructors\
**Created:** [September 27, 2024, 5:28pm UTC](https://discourse.julialang.org/t/constructors-for-parametric-types/119971 "2024-09-27T17:28:37Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![Maurizio\_Tomasi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/maurizio_tomasi/32/384_2.png) [@Maurizio\_Tomasi](https://discourse.julialang.org/u/Maurizio_Tomasi)\
**Post date:** [September 27, 2024, 5:28pm UTC](https://discourse.julialang.org/t/constructors-for-parametric-types/119971/1 "2024-09-27T17:28:37Z")

</div>

Hi all, I am studying how Julia handles constructors in parametric types and have found a puzzling behavior.

Consider this trivial type:

```julia
struct Test1{R <: Real}
    x::R
end

```

I can instantiate `Test1` either with or without stating `R` explicitly:

```julia
# Works
println(Test1(1.0))
# Works
println(Test1{Float64}(1.0))

```

If I provide an explicit inner constructor, the first case no longer works but the second one does:

```julia
struct Test2{R <: Real}
    x::R

    Test2{R}(r::R) where {R <: Real} = new{R}(r)
end

# Does not work, of course!
# println(Test2(1.0))
println(Test2{Float64}(1.0))

```

Here comes the puzzling part: suppose that I want my type to handle the case when the caller passes a complex number by just taking its real part:

```julia
struct Test3{R <: Real}
    x::R

    function Test3{C}(z::C) where {C <: Complex}
        r = real(z)
        R = typeof(r)

        return new{R}(r)
    end
end

# Why does this not work?
println(Test3{ComplexF64}(1.0 + 2.0im))

```

Although it seems to me that `Test3` closely matches the way the inner constructor for `Test2` was defined, this does not work and produces this error:

```julia
ERROR: LoadError: TypeError: in Test3, in R, expected R<:Real, got Type{ComplexF64}

```

The way to solve this problem is to drop `{C}` from the inner constructor:

```julia
struct Test4{R <: Real}
    x::R

    # No `Test4{C}` here, just `Test4`
    function Test4(z::C) where {C <: Complex}
        r = real(z)
        R = typeof(r)

        return new{R}(r)
    end
end

# Works!
println(Test4(1.0 + 2.0im))

```

I am curious about what’s happening in `Test3`: I do not understand why Julia still requires that the input type be `<: Real` when the inner constructor clearly says that it can be `<: Complex`. May somebody help me understand Julia’s behavior here?

---

<div class="post-metadata">

**Author:** ![eldee](https://avatars.discourse-cdn.com/v4/letter/e/b5a626/32.png) [@eldee](https://discourse.julialang.org/u/eldee)\
**Post date:** [September 27, 2024, 6:01pm UTC](https://discourse.julialang.org/t/constructors-for-parametric-types/119971/2 "2024-09-27T18:01:23Z")

</div>

Hi,

This issue is that a constructor `function Test3{C}(...)` should create an instance of type `Test3{C}`, regardless of the input arguments. The error then expresses that `C` (`ComplexF64`) does not satisfy the `<: Real` you have in `struct Test3{R <: Real}`.

Something like

```julia
struct Test5{R <: Real}
    x::R

    function Test5{R}(z::Complex{R}) where R
        r = real(z)
        return new{R}(r)
    end
end

println(Test5{Float64}(1.0 + 2.0im))
# Test5{Float64}(1.0)

```

will work just fine, even though the input type `Complex{Float64}` does not satisfy `<: Real`.

---

<div class="post-metadata">

**Author:** ![Maurizio\_Tomasi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/maurizio_tomasi/32/384_2.png) [@Maurizio\_Tomasi](https://discourse.julialang.org/u/Maurizio_Tomasi)\
**Post date:** [September 28, 2024, 6:46am UTC](https://discourse.julialang.org/t/constructors-for-parametric-types/119971/3 "2024-09-28T06:46:12Z")

</div>

Thank you @eldee, for your answer. So, if I understand correctly, it is not enough that the `new{R}(r)` statement within the `Test3{C}` inner constructor satisfies the requirement `R <: Real`, because the very definition of the constructor (`Test3{C}(z::C) where {C <: Complex}`) should have satisfied the same requirement too?

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [September 28, 2024, 7:50am UTC](https://discourse.julialang.org/t/constructors-for-parametric-types/119971/4 "2024-09-28T07:50:04Z")

</div>

> [@eldee](#):
>
> a constructor `function Test3{C}(...)` should create an instance of type `Test3{C}`

Julia doesn’t enforce this. As you say, the error comes from the type itself, before the constructor call even occurs. The constructor’s signature cannot widen the type’s parameters.

```julia
julia> struct X{T<:Real}
         function X{T}() where T<:Number
           return "hello"
         end
       end

julia> X{Int}()
"hello"

julia> X{Int}
X{Int64}

julia> X{Complex}
ERROR: TypeError: in X, in T, expected T<:Real, got Type{Complex}

```

---

<div class="post-metadata">

**Author:** ![Maurizio\_Tomasi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/maurizio_tomasi/32/384_2.png) [@Maurizio\_Tomasi](https://discourse.julialang.org/u/Maurizio_Tomasi)\
**Post date:** [September 28, 2024, 8:06am UTC](https://discourse.julialang.org/t/constructors-for-parametric-types/119971/5 "2024-09-28T08:06:19Z")

</div>

Thanks a lot, @Benny , your example really nails it!

I must confess that it would have been more useful to prevent the compilation of the inner constructor for `struct X{T<:Real}` instead of signaling the problem only once you try to refer to `X{Complex}`.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [September 28, 2024, 8:52am UTC](https://discourse.julialang.org/t/constructors-for-parametric-types/119971/6 "2024-09-28T08:52:11Z")

</div>

> [@Maurizio\_Tomasi](#):
>
> it would have been more useful to prevent the compilation of the inner constructor for `struct X{T<:Real}`

Compilation does not occur when definitions are evaluated; method compilation is triggered by `precompile` or calls. But yes it could be nice if the type bounds related to types are checked at definitions; this isn’t done for subtypes either, so manually match or narrow the type bounds on subtypes to stay sane. But on the other hand you would no longer be able to write `where T` as a shorthand because that’s an implicit `T<:Any`.
