How to get a tuple's field using a runtime index in a type stable way?

Are you sure that test(...) allocates?

julia> const tup_const = (10, 100, 1., 2., 3.)
(10, 100, 1.0, 2.0, 3.0)

julia> function test(tup, idx, type::Type{T}) where T
           ret::T = tup[idx]
           ret
       end
test (generic function with 1 method)

julia> @time test(tup_const, 3, Float64)
  0.000003 seconds
1.0

julia> @btime test(tup_const, 3, Float64)
  1.041 ns (0 allocations: 0 bytes)
1.0

You may also want to consider the effect of constant propagation.

Note the union type below.

julia> @code_warntype test(tup_const, 3, Float64)
MethodInstance for test(::Tuple{Int64, Int64, Float64, Float64, Float64}, ::Int64, ::Type{Float64})
  from test(tup, idx, type::Type{T}) where T @ Main REPL[27]:1
Static Parameters
  T = Float64
Arguments
  #self#::Core.Const(test)
  tup::Tuple{Int64, Int64, Float64, Float64, Float64}
  idx::Int64
  type::Core.Const(Float64)
Locals
  ret::Float64
  @_6::Union{Float64, Int64}
Body::Float64
1 ─      Core.NewvarNode(:(ret))
│   %2 = Base.getindex(tup, idx)::Union{Float64, Int64}
│        (@_6 = %2)
│   %4 = (@_6 isa $(Expr(:static_parameter, 1)))::Bool
└──      goto #3 if not %4
2 ─      goto #4
3 ─ %7 = Base.convert($(Expr(:static_parameter, 1)), @_6::Int64)::Float64
└──      (@_6 = Core.typeassert(%7, $(Expr(:static_parameter, 1))))
4 ┄      (ret = @_6::Float64)
└──      return ret

If we create a function with the index three hard-coded in, we see the absence of instability.

julia> function test2(tup, idx)
           tup[idx]
       end
test2 (generic function with 2 methods)

julia> f(tup) = @inline test2(tup, 3)
f (generic function with 1 method)

julia> @code_warntype f(tup_const)
MethodInstance for f(::Tuple{Int64, Int64, Float64, Float64, Float64})
  from f(tup) @ Main REPL[43]:1
Arguments
  #self#::Core.Const(f)
  tup::Tuple{Int64, Int64, Float64, Float64, Float64}
Locals
  val::Float64
Body::Float64
1 ─     nothing
│       (val = Main.test2(tup, 3))
│       nothing
└──     return val
1 Like