# Why can't a tuple of types have an abstract type with Type selectors?

**URL:** <https://discourse.julialang.org/t/why-cant-a-tuple-of-types-have-an-abstract-type-with-type-selectors/134036>\
**Category:** General Usage\
**Tags:** type, tuple, dispatch\
**Created:** [November 21, 2025, 11:13pm UTC](https://discourse.julialang.org/t/why-cant-a-tuple-of-types-have-an-abstract-type-with-type-selectors/134036 "2025-11-21T23:13:40Z")\
**Posts on this page:** 8\
**Page:** 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:** [November 21, 2025, 11:13pm UTC](https://discourse.julialang.org/t/why-cant-a-tuple-of-types-have-an-abstract-type-with-type-selectors/134036/1 "2025-11-21T23:13:40Z")

</div>

```julia-auto
julia> 1 isa Int
true

julia> (1,) isa Tuple{Int}
true

julia> Int isa Type{Int}
true

julia> (Int,) isa Tuple{Type{Int}} # ???
false

```

And how does dispatch work if this doesn’t work for method signatures as tuple types?

---

<div class="post-metadata">

**Author:** ![heliosdrm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/heliosdrm/32/3851_2.png) [@heliosdrm](https://discourse.julialang.org/u/heliosdrm)\
**Post date:** [November 22, 2025, 12:47am UTC](https://discourse.julialang.org/t/why-cant-a-tuple-of-types-have-an-abstract-type-with-type-selectors/134036/2 "2025-11-22T00:47:20Z")

</div>

`typeof((Int,))` is not `Tuple{Type{Int}}`, but `Tuple{DataType}`, and accordingly this does work:

```julia-auto
julia> (Int,) isa Tuple{DataType}
true

```

The point as I understand it is related to:

```julia-auto
julia> isconcretetype(DataType)
true

julia> isconcretetype(Type{Int})
false

```

Counterintuitive? I’d say yes, but [correctly documented](https://docs.julialang.org/en/v1/manual/types/#man-typet-type):

> For each type `T`, `Type{T}` is an **abstract** parametric type whose only instance is the object `T`.

(emphasis added).

This means that once you wrap a `DataType` (`Int`, `Float64`…) inside a `Tuple`, it is no longer possible to distinguish it from any other `DataType` by looking into the type of that tuple, just as you cannot distinguish `(1,)` from `(2,)` only by the tuple’s type - you have to access directly the members of the tuple to do it.

---

<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:** [November 22, 2025, 1:06am UTC](https://discourse.julialang.org/t/why-cant-a-tuple-of-types-have-an-abstract-type-with-type-selectors/134036/3 "2025-11-22T01:06:42Z")

</div>

The reason I would think it could work otherwise is that:

```julia-auto
julia> Type{Int} <: DataType <: Type
true

julia> [(Int,) isa Tuple{T} for T in (Type{Int}, DataType, Type)]
3-element Vector{Bool}:
 0
 1
 1

```

So while it’s true that the concrete tuple type only specifies `DataType`, I would expect tuple type covariance to apply to an abstract subtype as well as an abstract supertype.

To elaborate on how it seems to contradict method signatures:

```julia-auto
julia> foo(::Type{Int}) = 1;

julia> methods(foo)[1].sig
Tuple{typeof(foo), Type{Int64}}

julia> (foo, Int) isa methods(foo)[1].sig
false

julia> bar(::Int) = 1;

julia> (bar, 1) isa methods(bar)[1].sig
true

```

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [November 22, 2025, 8:22am UTC](https://discourse.julialang.org/t/why-cant-a-tuple-of-types-have-an-abstract-type-with-type-selectors/134036/4 "2025-11-22T08:22:13Z")

</div>

This is a long-standing gripe of mine, and it can cause real performance problems.

It’s also the source of ocassional confusion for people using BenchmarkTools.jl:

```julia-auto
julia> @btime convert(Int, 1.0)
  1.002 ns (0 allocations: 0 bytes)
1

julia> @btime convert($Int, 1.0)
  51.587 ns (0 allocations: 0 bytes)
1

```

The second one is slow because interally `$Int` got put into a `Tuple` which made the compiler not able to know what it was, so a dynamic dispatch was inserted on `DataType`.

The way I typically work around this is by wrapping types in Tuples with some thin wrapper:

```julia-auto
struct TWrapper{T} end
maybe_wrap(x) = x
maybe_wrap(::Type{T}) where {T} = TWrapper{T}()

maybe_unwrap(x) = x
maybe_unwrap(::TWrapper{T}) where {T} = T

wrapped_tuple(args...) = maybe_wrap.(args)

```

and then e.g.

```julia-auto
julia> typeof(wrapped_tuple(Int, 1, 2, Float64))
Tuple{TWrapper{Int64}, Int64, Int64, TWrapper{Float64}}

```

so now the type information isn’t being erased down into a pesky `DataType`.

---

<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:** [November 22, 2025, 7:16pm UTC](https://discourse.julialang.org/t/why-cant-a-tuple-of-types-have-an-abstract-type-with-type-selectors/134036/5 "2025-11-22T19:16:36Z")

</div>

> [@Mason](#):
>
> The way I typically work around this is by wrapping types in Tuples

It can also sometimes be handled with `@generated` functions, as in [Puzzling inference with `Base.Fix` · Issue #59928 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/59928)

---

<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:** [November 22, 2025, 11:39pm UTC](https://discourse.julialang.org/t/why-cant-a-tuple-of-types-have-an-abstract-type-with-type-selectors/134036/6 "2025-11-22T23:39:56Z")

</div>

> [@Mason](#):
>
> `Int` got put into a `Tuple` which made the compiler not able to know what it was, so a dynamic dispatch was inserted on `DataType`

I don’t think this has to correlate with the break in tuple covariance, but this also crops up often with the implementation of keyword arguments. The compiler can overcome it in narrow circumstances, but as of 1.12, `keyzero(;t::Type{T}) where T = zero(T)` is generally still type-unstable because a call lowers to `Core.kwcall(::@NamedTuple{t::DataType}, ::typeof(myzero))`. That’s despite the internal positional method (something like `var"#myzero#6"(t::Type{T}, ::typeof(myzero)) where T`) specializing over the type as stated by the Performance Tips section on the specialization heuristic. I usually just use positional arguments instead, but a similar fix by an instance with a type parameter would be `keyzero(;t::Val{T}) where T`.

---

<div class="post-metadata">

**Author:** ![heliosdrm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/heliosdrm/32/3851_2.png) [@heliosdrm](https://discourse.julialang.org/u/heliosdrm)\
**Post date:** [November 23, 2025, 10:57am UTC](https://discourse.julialang.org/t/why-cant-a-tuple-of-types-have-an-abstract-type-with-type-selectors/134036/7 "2025-11-23T10:57:55Z")

</div>

> [@Benny](#):
>
> is generally still type-unstable because a call lowers to `Core.kwcall(::@NamedTuple{t::DataType}, ::typeof(myzero))`

The implementation details are over my knowledge, but about this particular comment I wonder: if `DataType` is a concrete type, why is this considered type-unstable?

---

<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:** [November 23, 2025, 11:12am UTC](https://discourse.julialang.org/t/why-cant-a-tuple-of-types-have-an-abstract-type-with-type-selectors/134036/8 "2025-11-23T11:12:10Z")

</div>

`DataType` is only enough for type stability if we’re doing generic type computation, not instantiation. In this case, the downstream `zero(t::DataType)` can only be inferred as `Any`. The static parameter in `Type{T}` is the typical way for dispatch to preserve an input type for the compiler, but that information is lost in a container like `Tuple` and thus the following code that accesses its elements. If the following code is part of the same method that made the `Tuple` or is a callee method that gets inlined, then the compiler might still recognize the input type at a specific element or in splatting. It’s still not great to be at the mercy of inlining heuristics or internal implementations of benchmarking, keyword calls, etc., and for something we can semantically guarantee with instantiable parametric wrappers at the cost of extra writing.
