# Idiomatic way to avoid the closure capture bug?

**URL:** <https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435>\
**Category:** General Usage\
**Created:** [March 28, 2026, 11:10am UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435 "2026-03-28T11:10:51Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![araujoms](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/araujoms/32/217734_2.png) [@araujoms](https://discourse.julialang.org/u/araujoms)\
**Post date:** [March 28, 2026, 11:10am UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/1 "2026-03-28T11:10:51Z")

</div>

I’ve been looking for ways to generate tuples without falling prey to the infamous [closure capture bug](https://github.com/JuliaLang/julia/issues/15276). I’ve found essentially four different ways, and wanted to know if it makes a difference which one I use, or whether any is the idiomatic way to do it.

Suppose you want to do `ntuple(i -> i * par, Val(N))`. Then you could either

1

```julia
_par = par
return ntuple(i -> i * _par, Val(N))

```

2

```julia
f = Base.Fix2((i, _par) -> i * _par, par)
return ntuple(f, Val(N))

```

3

```julia
f = let par = par
    i -> i * par
end
return ntuple(f, Val(N))

```

4

```julia
let par = par
    return ntuple(i -> i * par, Val(N))
end

```

---

<div class="post-metadata">

**Author:** ![adienes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/adienes/32/37459_2.png) [@adienes](https://discourse.julialang.org/u/adienes)\
**Post date:** [March 28, 2026, 12:56pm UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/2 "2026-03-28T12:56:46Z")

</div>

I think 1 and 4 are most canonical

---

<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:** [March 28, 2026, 1:25pm UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/3 "2026-03-28T13:25:01Z")

</div>

`4` would be my preferred way to do it, but all the options you show here are going to be the same, other than how much or little they end up cluttering your namespace. 4. clutters the namespace the least, so that’s why I prefer it.

Sometimes I use this macro to make it a little cleaner:

````julia-auto
"""
   @localize args... expr

Writing
```
@localize x y z expr
```
is equivalent to writing
```
let x=x, y=y, z=z
    expr
end
```
This is useful for avoiding the boxing of captured variables when working with closures.

See https://juliafolds2.github.io/OhMyThreads.jl/stable/literate/boxing/boxing/ for more information about boxed variables.
"""
macro localize(args...)
    syms = args[1:end-1]
    ex = args[end]
    letargs = map(syms) do sym
        if !(sym isa Symbol)
            throw(ArgumentError("All but the final argument to `@localize` must be symbols! Got $sym"))
        end
        :($sym = $sym)
    end
    esc(:(let $(letargs...)
              $ex
          end))
end

````

With that, you could write

```julia-auto
@localize par ntuple(i -> i * par, Val(N))

```

---

<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:** [March 28, 2026, 1:32pm UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/4 "2026-03-28T13:32:42Z")

</div>

> [@Mason](#):
>
> but all the options you show here are going to be the same

Well, actually this statement is technically not quite true. Option 2 does actually carry the potential for a performance difference. The `Fix2` object only specializes on the type of `par`, not the value of `par`, so using it _can_ block constant propagation of `par`’s value in some circumstances.

* * *

Here’s an example where an anonymous function results in more efficient code than `Fix2`:

```julia
foo(f,a,b) = f(1) ? a / b : b / a;
greater_than_1(x) = x > 1;

```

This is efficient:

```julia-auto
julia> @code_llvm debuginfo=:none foo(greater_than_1, 1, 2)
; Function Signature: foo(typeof(Main.greater_than_1), Int64, Int64)
define double @julia_foo_4589(i64 signext %"a::Int64", i64 signext %"b::Int64") #0 {
top:
  %0 = sitofp i64 %"b::Int64" to double
  %1 = sitofp i64 %"a::Int64" to double
  %2 = fdiv double %0, %1
  ret double %2
}

```

This is less efficient:

```julia-auto
julia> @code_llvm debuginfo=:none foo(Base.Fix2(>, 1), 1, 2)
; Function Signature: foo(Base.Fix{2, typeof(Base.:(>)), Int64}, Int64, Int64)
define double @julia_foo_4592(ptr nocapture noundef nonnull readonly align 8 dereferenceable(8) %"f::Fix", i64 signext %"a::Int64", i64 signext %"b::Int64") #0 {
top:
  %"f::Fix.unbox" = load i64, ptr %"f::Fix", align 8
  %0 = icmp sgt i64 %"f::Fix.unbox", 0
  br i1 %0, label %L8, label %L4

common.ret: ; preds = %L8, %L4
  %common.ret.op = phi double [%3, %L4], [%6, %L8]
  ret double %common.ret.op

L4: ; preds = %top
  %1 = sitofp i64 %"a::Int64" to double
  %2 = sitofp i64 %"b::Int64" to double
  %3 = fdiv double %1, %2
  br label %common.ret

L8: ; preds = %top
  %4 = sitofp i64 %"b::Int64" to double
  %5 = sitofp i64 %"a::Int64" to double
  %6 = fdiv double %4, %5
  br label %common.ret
}

```

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [March 28, 2026, 2:10pm UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/5 "2026-03-28T14:10:33Z")

</div>

> [@araujoms](#):
>
> 2
> 
> ```julia
> f = Base.Fix2((i, _par) -> i * _par, par)
> return ntuple(f, Val(N))
> 
> ```

Instead of `(i, _par) -> i * _par`, just write `*`. Then the entire example **2** reduces to just:

```julia
return ntuple(Base.Fix2(*, par), Val(N))

```

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [March 28, 2026, 2:13pm UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/6 "2026-03-28T14:13:43Z")

</div>

You’ll almost always avoid it if you keep to simpler variable lifetimes and assignments. I’ve found it makes my code more readable for humans, too!

E.g., instead of assigning the same name many times inside lots of different `if` branches, refactor to functions or expressions that return or evaluate to the value(s) you want and assign the name once.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [March 28, 2026, 2:23pm UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/7 "2026-03-28T14:23:10Z")

</div>

> [@Mason](#):
>
> Well, actually this statement is technically not quite true. Option 2 does actually carry the potential for a performance difference. The `Fix2` object only specializes on the type of `par`, not the value of `par`, so using it _can_ block constant propagation of `par`’s value in some circumstances.

While this is something to be aware of, I will clarify that this does _not_ mean that `Base.Fix` is less efficient than closures. It is just a gotcha to keep in mind, both while programming and while checking performance. And the gotcha is not specific to `Base.Fix`, rather it is a more general property of Julia. Programmers who care about performance in Julia simply must be aware of types in their code.

Alternatively, taking your post as an argument against `Base.Fix`, which I am not saying it is, but hypothetically taking it as such, the argument would be a straw man.

A fair comparison leads to identical `code_llvm`:

```julia-repl
julia> @code_llvm debuginfo=:none ((x, y) -> foo(greater_than_1, x, y))(1, 2)
; Function Signature: var"#2"(Int64, Int64)
define double @"julia_#2_0"(i64 signext %"x::Int64", i64 signext %"y::Int64") local_unnamed_addr #0 {
top:
  %0 = sitofp i64 %"y::Int64" to double
  %1 = sitofp i64 %"x::Int64" to double
  %2 = fdiv double %0, %1
  ret double %2
}

julia> @code_llvm debuginfo=:none ((x, y) -> foo(Base.Fix2(>, 1), x, y))(1, 2)
; Function Signature: var"#5"(Int64, Int64)
define double @"julia_#5_0"(i64 signext %"x::Int64", i64 signext %"y::Int64") local_unnamed_addr #0 {
top:
  %0 = sitofp i64 %"y::Int64" to double
  %1 = sitofp i64 %"x::Int64" to double
  %2 = fdiv double %0, %1
  ret double %2
}

```

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [March 28, 2026, 2:25pm UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/8 "2026-03-28T14:25:02Z")

</div>

I now tend to believe that `let` block and the `local` keyword are redundant constructs. You can always write a separate function (called function barrier) to introduce local variable.

So I think that closure capture bug is rendered less important.

---

<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:** [March 28, 2026, 2:33pm UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/9 "2026-03-28T14:33:35Z")

</div>

> [@nsajko](#):
>
> While this is something to be aware of, I will clarify that this does _not_ mean that `Base.Fix` is less efficient than closures. It is just a gotcha to keep in mind, both while programming and while checking performance.

Agreed. I was just clarifying because @araujoms asked if there was a difference between the approaches, and from that PoV, option 2 is the only one that has any _potential_ differences in codegen, whereas the others are just stylistic changes.

I agree though that the differences with `Fix1`/`Fix2`/`Returns`/… are pretty minor though, and in the overwhelming majority of cases are not going to have any appreciable differences in codegen.

But it’s something to be aware of whenever passing around structs that wrap data, rather than embedding that data in a function body directly, because if the struct ever gets passed through a function barrier where it fails to inline, then there’s more cases where you can get under-specialized code.

---

<div class="post-metadata">

**Author:** ![araujoms](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/araujoms/32/217734_2.png) [@araujoms](https://discourse.julialang.org/u/araujoms)\
**Post date:** [March 28, 2026, 6:23pm UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/10 "2026-03-28T18:23:35Z")

</div>

Thanks for all the answers! I’m glad it doesn’t really make a difference.

2 is the way recommended in the [SciML style guide](https://docs.sciml.ai/SciMLStyle/stable/#Closures-should-be-avoided-whenever-possible), and it’s most inconvenient to use (specially if there are several parameters to fix).

3 is the way recommended in the [Julia manual](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-captured). While in this simple example it’s quite ugly, I think it makes sense when you need to build a complicated function.

Otherwise I think 4 is indeed the best.

---

<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:** [March 29, 2026, 2:08am UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/11 "2026-03-29T02:08:47Z")

</div>

The Julia manual only writes it like 3 because the closure needed to be returned, not immediately called by a higher-order function. Moving the higher-order function call, `ntuple` here, into the `let` block like 4 is typical, if not moreso.

2 is preferred by some because it doesn’t involve a scope capturing variables at all, just a structure containing a value as the writer may intend. While these small examples are all equivalent for its purpose, further adding an assignment inside the `let` block or another closure to do something else ruins the no-reassignment optimization, often by accident. I ran into this in Base before, it ended up getting fixed by 1 to avoid layering more blocks. 2 isn’t done more because `Fix{N}`-ing one positional argument is often not feasible and warrants custom functors.

---

<div class="post-metadata">

**Author:** ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)\
**Post date:** [March 29, 2026, 5:14am UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/12 "2026-03-29T05:14:25Z")

</div>

> [@mbauman](#):
>
> instead of assigning the same name many times inside lots of different `if` branches, refactor to functions or expressions that return or evaluate to the value(s) you want and assign the name once

👍  
For this it would be nice if the `const` keyword were allowed in local scope to indicate that “I really don’t want to reassign this variable.” Unfortunately, after more than 10 years, there’s [still no agreement about the implementation details](https://github.com/JuliaLang/julia/issues/5148).

---

<div class="post-metadata">

**Author:** ![araujoms](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/araujoms/32/217734_2.png) [@araujoms](https://discourse.julialang.org/u/araujoms)\
**Post date:** [March 29, 2026, 8:52am UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/13 "2026-03-29T08:52:38Z")

</div>

What’s the no-reassignment optimization?

---

<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:** [March 29, 2026, 3:32pm UTC](https://discourse.julialang.org/t/idiomatic-way-to-avoid-the-closure-capture-bug/136435/14 "2026-03-29T15:32:53Z")

</div>

All of the approaches except `Fix` involve creating a new variable that is only assigned once during or after declaration, never reassigned. That lets the lowerer implement the closure as a `struct` instance with the captured variable represented as a parametric immutable field. I called it an optimization because otherwise, it becomes an untyped mutable field to store reassignments, which could occur in other closures with no explicit types. A common intuition is that inferable closures ought to help, but like any other function, different input types would result in different inferred types, and the captured variable needs to accommodate all possibilities. There are possible implementation improvements with regards to prior reassignments in the same scope, calling closures that don’t escape, or explicit types.

> [@PatrickHaecker](#):
>
> still no agreement about the implementation details

Global `const` reassignments being possible across world ages now makes the keyword less suitable for catching unintended reassignments. Still think it’d have been really nice if there was a consistent and required syntax for variable declaration, maybe even definitions, that can’t occur twice in the same scope; that more traditional approach lets accumulating for-loops look the same in functions versus the global scope for instance.
