# Type-stable \`(...)\` tuples of types?

**URL:** <https://discourse.julialang.org/t/type-stable-tuples-of-types/133361>\
**Category:** General Usage\
**Tags:** question\
**Created:** [October 22, 2025, 3:21pm UTC](https://discourse.julialang.org/t/type-stable-tuples-of-types/133361 "2025-10-22T15:21:20Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![mlange-42](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mlange-42/32/219037_2.png) [@mlange-42](https://discourse.julialang.org/u/mlange-42)\
**Post date:** [October 22, 2025, 3:21pm UTC](https://discourse.julialang.org/t/type-stable-tuples-of-types/133361/1 "2025-10-22T15:21:20Z")

</div>

Hi all!

I have an ECS (entity component system) API and try to make type-stable use of tuples of types. Currently, I have it working well with VarArgs, for example:

```julia
pos, vel = get_components(world, entity, Position, Velocity)

```

However, I would prefer a tuple for the types, like this:

```julia
pos, vel = get_components(world, entity, (Position, Velocity))

```

Unfortunately, I am not able to get this working in a type-stable way.

My current working var-args approach looke like this:

```julia
unction get_components(world::World{CS,CT,N}, entity::Entity, comp_types::Type...) where {CS<:Tuple,CT<:Tuple,N}
    if !is_alive(world, entity)
        error("can't get components of a dead entity")
    end
    return _get_components(world, entity, Val{Tuple{comp_types...}}())
end

@generated function _get_components(world::World{CS,CT,N}, entity::Entity, ::Val{TS}) where {CS<:Tuple,CT<:Tuple,N,TS<:Tuple}
    types = TS.parameters
    if length(types) == 0
        return :(())
    end

    exprs = Expr[]
    push!(exprs, :(idx = world._entities[entity._id]))

    for i in 1:length(types)
        T = types[i]
        stor_sym = Symbol("stor", i)
        col_sym = Symbol("col", i)
        val_sym = Symbol("v", i)

        push!(exprs, :(
            $(stor_sym) = _get_storage(world, Val{$(QuoteNode(T))}())
        ))
        push!(exprs, :(
            $(col_sym) = $(stor_sym).data[idx.archetype]
        ))
        push!(exprs, :(
            $(val_sym) = $(col_sym)._data[idx.row]
        ))
    end

    vals = [:($(Symbol("v", i))) for i in 1:length(types)]
    push!(exprs, Expr(:return, Expr(:tuple, vals...)))

    return quote
        @inbounds begin
            $(Expr(:block, exprs...))
        end
    end
end

```

I found two ways to use tuples in the API, but both are extemely slow due to type instability:

```julia
function get_components(world::World{CS,CT,N}, entity::Entity, comp_types::Tuple{Vararg{Type}}) where {CS<:Tuple,CT<:Tuple,N}
    if !is_alive(world, entity)
        error("can't get components of a dead entity")
    end
    return _get_components(world, entity, Val{Tuple{comp_types...}}())
end

@generated function _get_components(world::World{CS,CT,N}, entity::Entity, ::Val{TS}) where {CS<:Tuple,CT<:Tuple,N,TS<:Tuple}

```

```julia
function get_components(world::World{CS,CT,N}, entity::Entity, comp_types::Tuple{Vararg{Type}}) where {CS<:Tuple,CT<:Tuple,N}
    if !is_alive(world, entity)
        error("can't get components of a dead entity")
    end
    return _get_components(world, entity, Tuple{comp_types...})
end

@generated function _get_components(world::World{CS,CT,N}, entity::Entity, ::Type{TS}) where {CS<:Tuple,CT<:Tuple,N,TS<:Tuple}

```

So is there any way to properly convert my `(Position, Velocity)` to `Tuple{Position, Velocity}` (I guess) in a type-stable way?

Thank you in advance!

---

<div class="post-metadata">

**Author:** ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)\
**Post date:** [October 22, 2025, 3:33pm UTC](https://discourse.julialang.org/t/type-stable-tuples-of-types/133361/2 "2025-10-22T15:33:11Z")

</div>

A possible way to side-step this is using `Val` in your tuple. I suspect this is the only way to make the generated function approach work well because a generated function has access to the types so without the `Val`, everything is just `DataType`. So my best guess is that ` get_components(world, entity, (Val(Position), Val(Velocity)))` would work fine, and you could make this macro if you want `@get_components(world, entity, (Position, Velocity))` for convenience.

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [October 22, 2025, 3:49pm UTC](https://discourse.julialang.org/t/type-stable-tuples-of-types/133361/3 "2025-10-22T15:49:29Z")

</div>

There was a trick here with `@generated`.: [Puzzling inference with `Base.Fix` · Issue #59928 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/59928)

And the `Base.Fix` uses a `Base._stable_typeof` to handle some of the problems with types as arguments. I.e. `typeof(Int)` returns `DataType`, whereas `_stable_typeof(Int)` returns the sharper `Type{Int}`.

One of the basic problems is that you can’t make `Tuple{Type{Int}}((Int,))` without losing the type information. It can be done with `Val`, or with `NamedTuple` with an explicit constructor like `NamedTuple{(:a, :b), Tuple{Type{Int8}, Type{Int16}}}((Int8, Int16))`. It can then be accessed with `fieldtypes` instead of `.parameters`.

---

<div class="post-metadata">

**Author:** ![mlange-42](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mlange-42/32/219037_2.png) [@mlange-42](https://discourse.julialang.org/u/mlange-42)\
**Post date:** [October 22, 2025, 6:58pm UTC](https://discourse.julialang.org/t/type-stable-tuples-of-types/133361/4 "2025-10-22T18:58:14Z")

</div>

Thank you! We settled for the `Val` variant in the API.
