# Function definition inside let-block impacting performance?

**URL:** <https://discourse.julialang.org/t/function-definition-inside-let-block-impacting-performance/110007>\
**Category:** Performance\
**Tags:** question\
**Created:** [February 9, 2024, 8:06pm UTC](https://discourse.julialang.org/t/function-definition-inside-let-block-impacting-performance/110007 "2024-02-09T20:06:28Z")\
**Posts on this page:** 1\
**Showing post:** 22

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 12, 2024, 3:53am UTC](https://discourse.julialang.org/t/function-definition-inside-let-block-impacting-performance/110007/22 "2024-02-12T03:53:55Z")

</div>

> [@mnemnion](#):
>
> But it surfaces what looks like a basic flaw in how Julia implements local scope, one which shows up in a number of places: the bad performance for closures which capture values which might be shared, the strange/wrong consequences of conditional method definitions, and the mandatory boxing of self-calls to a function

These issues are loosely related, but I don’t see them stemming from the same problem or having the same solution.

1. **Closure poor performance with shared captures:**  
Boxing (heap allocation) is necessary for values that could get reassigned during the closure’s lifetime, since the semantic is that a captured variable that changes value must reflect this everywhere that it can be seen. Most of the performance loss we see is actually due to type instability from not type-parameterizing the box, which happens because lowering doesn’t have access to function return types.  
Solutions: [type-annotate boxed captures or use the `let` block trick](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-captured), and either [rewrite lowering in Julia](https://github.com/JuliaLang/julia/issues/15276#issuecomment-922435607) so it accesses return types or have lowering [emit Julia code](https://discourse.julialang.org/t/rfc-some-ideas-to-tackle-15276-performance-of-captured-variables-in-closures/95260) which does so.
2. **Conditional function definition weirdness:**  
Two versions of local `foo()` defined in two branches of a conditional could easily become instances of two structs (e.g. `var"#foo#3"` and `var"#foo#4"`) declared at global scope, and the local variable `foo` would simply be assigned to one or the other instance. However, Julia’s multiple dispatch paradigm affords us the ability to declare additional methods of `foo`, and if those additional methods were declared in yet other conditionals … you see [the issue](https://github.com/JuliaLang/julia/issues/15602#issuecomment-226518246).  
Solution: resolve the `local const` [issue](https://github.com/JuliaLang/julia/issues/5148), and then treat local functions as local constants (and allow conditionally-added methods _only_ if they are added within the same branch as the initial function declaration).
3. **Mandatory boxing of local function self-calls:**  
As I’ve [shown](https://discourse.julialang.org/t/function-definition-inside-let-block-impacting-performance/110007/15), having the function capture itself at all is totally unnecessary if the function’s local identifier is never reassigned (i.e. almost always). It happens as a consequence of the rule that when a local function captures a local variable, if that variable has any syntactical assignment after the function’s instantiation, then it should be boxed (this is a good rule). However, even though the first assignment of the function’s local identifier _technically_ happens after the object’s instantiation, it’s not assigned to any value that isn’t known to the function.  
Solution: carve out an exemption for the first assignment of the identifier which represents the local function itself.

---

_[View the full topic](https://discourse.julialang.org/t/function-definition-inside-let-block-impacting-performance/110007)._
