# For statement type instability

**URL:** <https://discourse.julialang.org/t/for-statement-type-instability/118817>\
**Category:** General Usage\
**Tags:** question, bug, performance\
**Created:** [August 30, 2024, 3:49pm UTC](https://discourse.julialang.org/t/for-statement-type-instability/118817 "2024-08-30T15:49:55Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![0samuraiE](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/0samuraie/32/209825_2.png) [@0samuraiE](https://discourse.julialang.org/u/0samuraiE)\
**Post date:** [August 30, 2024, 3:49pm UTC](https://discourse.julialang.org/t/for-statement-type-instability/118817/1 "2024-08-30T15:49:55Z")

</div>

Hello all. I encountered the same problem as in the title. Sorry if this is a known problem.  
[Added later]  
`@code_warntype` warns `@_2::Union{Nothing, Tuple{Union{One, Two}, Int64}}` has type instability.  
How can I solve this problem?

```julia
abstract type AbstractNumber end

struct One <: AbstractNumber end
struct Two <: AbstractNumber end

function test()
    for number in (One(), Two())
    end
end

```

```julia
julia> @code_warntype test()
MethodInstance for test()
  from test() @ Main ~/dev/test/test.jl:6
Arguments
  #self#::Core.Const(test)
Locals
  @_2::Union{Nothing, Tuple{Union{One, Two}, Int64}}
  number::Union{One, Two}
Body::Nothing
1 ─ %1 = Main.One()::Core.Const(One())
│ %2 = Main.Two()::Core.Const(Two())
│ %3 = Core.tuple(%1, %2)::Core.Const((One(), Two()))
│ (@_2 = Base.iterate(%3))
│ %5 = (@_2::Core.Const((One(), 2)) === nothing)::Core.Const(false)
│ %6 = Base.not_int(%5)::Core.Const(true)
└── goto #4 if not %6
2 ┄ %8 = @_2::Union{Tuple{One, Int64}, Tuple{Two, Int64}}
│ (number = Core.getfield(%8, 1))
│ %10 = Core.getfield(%8, 2)::Int64
│ (@_2 = Base.iterate(%3, %10))
│ %12 = (@_2 === nothing)::Bool
│ %13 = Base.not_int(%12)::Bool
└── goto #4 if not %13
3 ─ goto #2
4 ┄ return nothing

```

---

<div class="post-metadata">

**Author:** ![mrufsvold](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mrufsvold/32/31600_2.png) [@mrufsvold](https://discourse.julialang.org/u/mrufsvold)\
**Post date:** [August 30, 2024, 3:53pm UTC](https://discourse.julialang.org/t/for-statement-type-instability/118817/2 "2024-08-30T15:53:28Z")

</div>

You are looping over a collect of multiple types so `number` has different types, but I’m not sure what your question is or what you’re trying to achieve?

---

<div class="post-metadata">

**Author:** ![0samuraiE](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/0samuraie/32/209825_2.png) [@0samuraiE](https://discourse.julialang.org/u/0samuraiE)\
**Post date:** [August 30, 2024, 3:58pm UTC](https://discourse.julialang.org/t/for-statement-type-instability/118817/3 "2024-08-30T15:58:34Z")

</div>

I am sorry the lack of clarity.  
`@code_warntype` warns `@_2::Union{Nothing, Tuple{Union{One, Two}, Int64}}` has type instability.  
How can I solve this problem?

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [August 30, 2024, 4:29pm UTC](https://discourse.julialang.org/t/for-statement-type-instability/118817/4 "2024-08-30T16:29:21Z")

</div>

You don’t need to do anything. In this case Julia will just unroll the loop later down the compilation pipeline and you won’t notice anything from this type-instability. In any case, the number of types is also small enough for union-splitting (where the magic number is 4 IIRC).

As a general rule of thumb: Small unions of concrete types are likely fine. You don’t need to fix anything. I’d say fixing “type instabilities” just for the sake of it is rather pointless. If you want to do it for performance optimization, you need to demonstrate that this is a performance problem to begin with.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [August 30, 2024, 5:29pm UTC](https://discourse.julialang.org/t/for-statement-type-instability/118817/5 "2024-08-30T17:29:10Z")

</div>

That being said, in general you should strive to avoid code like this in Julia:

1. generally avoid [containers of abstract/heterogeneous types](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-abstract-container) … yes, there are specialized cases like `Vector{Union{Missing,T}}` that the compiler does a good job with thanks to union-splitting, but they are the exception that proves the rule
2. be cautious about using types to encode _values_ as in `One <: AbstractNumber`, `Two <: AbstractNumber`. This can be useful, e.g. `π` has its own type, but it’s a specialized tool because of point (1)

---

<div class="post-metadata">

**Author:** ![0samuraiE](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/0samuraie/32/209825_2.png) [@0samuraiE](https://discourse.julialang.org/u/0samuraiE)\
**Post date:** [August 31, 2024, 2:20am UTC](https://discourse.julialang.org/t/for-statement-type-instability/118817/6 "2024-08-31T02:20:34Z")

</div>

Thank you for answers.  
This code example is a simplified version of my original code, so it does not make much sense.

@abraemer  
In fact, my original code caused an unintended memory allocation that I thought should be resolved. In the end, by adding `@inline`, I was able to properly optimize the code and eliminate the memory allocations.  
Looking at the Julia base functions, `@inline` is rarely used, so I didn’t think it was necessary.

@stevengj  
I knew about that article, but did not know that the rule applied to Tuple.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [August 31, 2024, 12:45pm UTC](https://discourse.julialang.org/t/for-statement-type-instability/118817/7 "2024-08-31T12:45:28Z")

</div>

> [@0samuraiE](#):
>
> I knew about that article, but did not know that the rule applied to Tuple.

It’s true that tuples of heterogeneous types are totally fine, as long as the tuple type is known to the compiler; it’s no different than a `struct` with heterogeneous members. But you have to be very careful about _iteration_ over a heterogeneous tuple.

`for` loops over heterogeneous tuples, in particular, are not unrolled (even though the compiler knows the length) and can lead to type-instability issues for heterogeneous tuples. Various threads have complained about this, e.g. [Unrolling loops over tuples - why so hard? - #15 by matthias314](https://discourse.julialang.org/t/unrolling-loops-over-tuples-why-so-hard/103428/15) and [Manually unroll operations with objects of tuple - #2 by mauro3](https://discourse.julialang.org/t/manually-unroll-operations-with-objects-of-tuple/11604/2), and there is an old package [Unrolled.jl](https://github.com/cstjean/Unrolled.jl) that tries to do this for you.

However, what’s less known is that certain functions like the `map`, `mapreduce`, and (as of Julia 1.8 thanks to [julia#31901](https://github.com/JuliaLang/julia/pull/31901)) `foreach` functions _can be_ completely unrolled for tuple arguments, so e.g.

```julia
foreach((One(), Two(),3,"4")) do number
    
end

```

is completely unrolled IIRC, and it can potentially be type stable depending on what you _do_ inside the loop. (One difficulty with `foreach` is that, because the “loop body” is a closure, you can easily get bitten by [julia#15276](https://github.com/JuliaLang/julia/issues/15276) if you want to assign to a local variable inside the loop.)

---

<div class="post-metadata">

**Author:** ![0samuraiE](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/0samuraie/32/209825_2.png) [@0samuraiE](https://discourse.julialang.org/u/0samuraiE)\
**Post date:** [September 1, 2024, 6:30am UTC](https://discourse.julialang.org/t/for-statement-type-instability/118817/8 "2024-09-01T06:30:46Z")

</div>

Thank you for precious information!!

---

<div class="post-metadata">

**Author:** ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)\
**Post date:** [September 12, 2024, 1:31pm UTC](https://discourse.julialang.org/t/for-statement-type-instability/118817/9 "2024-09-12T13:31:44Z")

</div>

> [@stevengj](#):
>
> `for` loops over heterogeneous tuples, in particular, are not unrolled (even though the compiler knows the length) and can lead to type-instability issues

This is intriguing, but is it even better to be type-stable here? Yes, it seems faster (and I saw the other thread, and opened PR on it for printing), but if I understand this then the loop will be unrolled, and does that mean many compiled versions? I.e. a lot more code? For maybe little gain, and could it be worse in the end?

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [September 12, 2024, 2:44pm UTC](https://discourse.julialang.org/t/for-statement-type-instability/118817/10 "2024-09-12T14:44:32Z")

</div>

> [@Palli](#):
>
> This is intriguing, but is it even better to be type-stable here?

It _is_ type stable to unroll a loop over a heterogeneous tuple. The compiler knows the types of the tuple entries.

---

<div class="post-metadata">

**Author:** ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)\
**Post date:** [September 12, 2024, 4:24pm UTC](https://discourse.julialang.org/t/for-statement-type-instability/118817/11 "2024-09-12T16:24:14Z")

</div>

I’m not saying it’s not type-stable, or that it’s worse per se, just if unrolled possibly (and assumed the alternative when not type-stable wouldn’t be unrolled).

The question was about if the unrolling might be bad; i.e. using `foreach`. With `for` is unstable right, and not unrolled?
