# Type instability in parametric struct

**URL:** <https://discourse.julialang.org/t/type-instability-in-parametric-struct/116266>\
**Category:** General Usage\
**Tags:** parametric-types, type-stability\
**Created:** [June 26, 2024, 2:46pm UTC](https://discourse.julialang.org/t/type-instability-in-parametric-struct/116266 "2024-06-26T14:46:49Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![moble](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/moble/32/23535_2.png) [@moble](https://discourse.julialang.org/u/moble)\
**Post date:** [June 26, 2024, 2:46pm UTC](https://discourse.julialang.org/t/type-instability-in-parametric-struct/116266/1 "2024-06-26T14:46:49Z")

</div>

I have a struct where one field is some collection, and another is just a single element of the collection’s `eltype`. (The real application is more complicated, but I describe the MWE.) I need to use the collection’s type frequently, and I _really_ need to ensure that functions involving this struct specialize, so I parametrize the struct with that type, and then try to specify the types of the fields:

```julia
struct X{CT}
    a::CT
    b::eltype(CT)
end

```

The problem is that the fieldtypes are not properly inferred:

```julia
julia> fieldtypes(X{Vector{Float64}})
(Vector{Float64}, Any)

```

This has huge negative effects when I try to actually use that `b` field. I’ve tried to annotate the following function in every way I can think of, but there’s always an `Any` in there.

```julia
julia> b(x::CT) where CT = x.b

julia> @code_warntype b(X([1.0, 2.0], 3.0))
MethodInstance for b(::X{Vector{Float64}})
  from b(x::CT) where CT @ Main REPL[6]:1
Static Parameters
  CT = X{Vector{Float64}}
Arguments
  #self#::Core.Const(b)
  x::X{Vector{Float64}}
Body::Any
1 ─ %1 = Base.getproperty(x, :b)::Any
└── return %1

```

Even if I specify the return type, there’s still an `Any` that gets converted, but it still appears to be slow and leads to allocations. Is there any way to get around this that doesn’t involve changing the parameters of the original struct?

> **versioninfo**
>
> ```julia
> julia> versioninfo()
> Julia Version 1.10.4
> Commit 48d4fd48430 (2024-06-04 10:41 UTC)
> Build Info:
> Official https://julialang.org/ release
> Platform Info:
> OS: macOS (arm64-apple-darwin22.4.0)
> CPU: 12 × Apple M2 Max
> WORD_SIZE: 64
> LIBM: libopenlibm
> LLVM: libLLVM-15.0.7 (ORCJIT, apple-m1)
> Threads: 1 default, 0 interactive, 1 GC (on 8 virtual cores)
> 
> ```

---

<div class="post-metadata">

**Author:** ![favba](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/favba/32/2735_2.png) [@favba](https://discourse.julialang.org/u/favba)\
**Post date:** [June 26, 2024, 3:14pm UTC](https://discourse.julialang.org/t/type-instability-in-parametric-struct/116266/2 "2024-06-26T15:14:25Z")

</div>

I don’t think there is a way around that. I believe you must change the Struct definition to

```julia
struct X{CT,ET}
    a::CT
    b::ET

    # This inner constructor definition prevents us from constructing the struct if eltype(CT) != ET
    function X(a::CT,b) where CT
        cb = convert(eltype(CT),b)
        return new{CT,eltype(CT)}(a,cb)
    end
end
```

---

<div class="post-metadata">

**Author:** ![zym1656205118](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zym1656205118/32/210343_2.png) [@zym1656205118](https://discourse.julialang.org/u/zym1656205118)\
**Post date:** [June 30, 2024, 2:50am UTC](https://discourse.julialang.org/t/type-instability-in-parametric-struct/116266/3 "2024-06-30T02:50:23Z")

</div>

Since `eltype` is a function, it cannot be used directly as part of a type parameter in type definitions. This is because type parameters are a compile-time concept, while function calls are executed at runtime.

I don’t know if this is what you need.

```julia
struct MyStruct{C, T}
    a::C
    b::T
    function MyStruct(a::C, b::T) where {C, T}
        if eltype(C) != T
            throw(TypeError(:MyStruct, "element type mismatch", T, eltype(C)))
        end
        new{C, T}(a, b)
    end
end

```

```julia
b(x::MyStruct) = x.b

```

```julia
@code_warntype b(MyStruct([1, 2, 3], 4))

```

```julia
MethodInstance for b(::MyStruct{Vector{Int64}, Int64})
  from b(x::MyStruct) @ Main In[6]:1
Arguments
  #self#::Core.Const(b)
  x::MyStruct{Vector{Int64}, Int64}
Body::Int64
1 ─ %1 = Base.getproperty(x, :b)::Int64
└── return %1

```

---

<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:** [June 30, 2024, 3:43am UTC](https://discourse.julialang.org/t/type-instability-in-parametric-struct/116266/4 "2024-06-30T03:43:16Z")

</div>

I hate that this is silent. This is what often happens when you try to put function calls in annotations:

```julia
julia> struct Y{N}
           a::N
           b::(N+1)
       end
ERROR: MethodError: no method matching +(::TypeVar, ::Int64)

```

The definition actually immediately tries to execute the call, but most methods reasonably don’t work on `TypeVar` type parameters. No error was thrown by `X` and the annotation became `::Any` because `eltype(::TypeVar)` actually reaches the fallback `eltype(x) = Any` method. I however have no good ideas for an error a `struct` definition could throw in general to catch these things, and function calls not involving `TypeVar` could be useful in annotations.

Your intent seemed to be enforcing a constraint at instantiation and trimming a “redundant” type parameter, but annotations can’t do that because methods are redefineable (and very little of Julia are the fixed intrinsics and builtins). Let’s say I instantiate a `X{MyOnes}(MyOnes(), 1im)`, but then I redefine `eltype(::Type{MyOnes}) = Bool` and instantiate a `X{MyOnes}(MyOnes(), true)`. Now `X{MyOnes}` has 2 instances with _different_ structures, this can’t have a good implementation anymore. That second parameter is not actually redundant after all.

---

<div class="post-metadata">

**Author:** ![moble](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/moble/32/23535_2.png) [@moble](https://discourse.julialang.org/u/moble)\
**Post date:** [June 30, 2024, 3:00pm UTC](https://discourse.julialang.org/t/type-instability-in-parametric-struct/116266/5 "2024-06-30T15:00:28Z")

</div>

Thanks for the comments everyone. I had seen this kind of annotation on a struct before, and assumed it worked properly by analogy with what happens in default arguments to a function. Maybe the place I had seen this annotation was also using `@computed` from [ComputedFieldTypes.jl](https://github.com/vtjnash/ComputedFieldTypes.jl?tab=readme-ov-file#examples).

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [June 30, 2024, 6:07pm UTC](https://discourse.julialang.org/t/type-instability-in-parametric-struct/116266/6 "2024-06-30T18:07:41Z")

</div>

> [@moble](#):
>
> ```julia
> struct X{CT}
> a::CT
> b::eltype(CT)
> end
> 
> ```

I would have thought something like

```julia
struct X{T, S<:AbstractArray{T}}
    a::T
    b::S
end

```

would be a possible solution. Or must it be more general than an array?

---

<div class="post-metadata">

**Author:** ![moble](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/moble/32/23535_2.png) [@moble](https://discourse.julialang.org/u/moble)\
**Post date:** [June 30, 2024, 6:23pm UTC](https://discourse.julialang.org/t/type-instability-in-parametric-struct/116266/7 "2024-06-30T18:23:06Z")

</div>

Yeah, it has to be more general than `AbstractArray`. And, ideally, I was hoping to do it without having to change the type parameters, as there are downstream issues to deal with. Oh well.

---

<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:** [June 30, 2024, 8:54pm UTC](https://discourse.julialang.org/t/type-instability-in-parametric-struct/116266/8 "2024-06-30T20:54:49Z")

</div>

> [@moble](#):
>
> Maybe the place I had seen this annotation was also using `@computed` from [ComputedFieldTypes.jl](https://github.com/vtjnash/ComputedFieldTypes.jl?tab=readme-ov-file#examples).

There are a few macros that support more annotation features, but yes they all get transformed elsewhere in the definition.
