Function definition inside let-block impacting performance?

If you mean along with possible runtime typeassert errors, then yes, concrete argument annotations can box 1 method into 1 possible specialization with 1 possible return type.

You didn’t get my point. Boxing in this case occurred for captured variables that were not yet assigned to a 2nd closure when the 1st closure was instantiated; in the fib case the 1st and 2nd closure are the same. To oversimplify the thread, uniment suggested inserting the 2nd closure’s lifted type into a typed box, while I suggested instantiation of the 2nd closure from the lifted type in the 1st closure’s lifted method, possibly both ways. That is all predicated on the closure name being assigned once ever, like a function in the global scope. The more common reason for boxing is the variable being reassigned (like foo() = 1 then foo = 2), and inference is fundamentally limited there; the upper limit is the lowerer proving all reassignments’ right hand sides never involve unpredictable closure variables and letting the compiler infer the type, otherwise it must be ::Any. In short, boxing occurs over captured variable assignments, not whether the function instance itself has multiple methods, so a unimethod would not help and would be boxed for the same reasons.

Maybe it will simplify this discussion to show that functions don’t usually need to be boxed, even if they are locally declared, have multiple methods, and are captured:

julia> fun = let
           g=f  # new local id needed because lowering capture logic needs work
           (args...) -> g(args...)
#3 (generic function with 1 method)

julia> fun.g  # no box
(::var"#f#4") (generic function with 2 methods)

julia> using BenchmarkTools
       @btime($fun($1)), @btime($fun($2,$3))
  1.800 ns (0 allocations: 0 bytes)
  1.800 ns (0 allocations: 0 bytes)
(1, 5)

Issue opened:

Unnecessary Box of Multi-Method Local Function · Issue #53338 · JuliaLang/julia (