# Does \`@label\`/\`@goto\` introduce scope like loop blocks?

**URL:** https://discourse.julialang.org/t/does-label-goto-introduce-scope-like-loop-blocks/129607
**Category:** General Usage
**Created:** [June 3, 2025, 5:23pm UTC](https://discourse.julialang.org/t/does-label-goto-introduce-scope-like-loop-blocks/129607 "2025-06-03T17:23:24Z")
**Posts on this page:** 6
**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: [June 3, 2025, 5:23pm UTC](https://discourse.julialang.org/t/does-label-goto-introduce-scope-like-loop-blocks/129607/1 "2025-06-03T17:23:24Z")

</div>

Variables assigned in a loop block scope are different across iterations while `if` statements don’t introduce new local scope at all, but what’s the variable scoping behavior for `@label`-`@goto` jumps? Initially I’d assume they just jump in and out of scopes created by other blocks, like in this example of 2 closures capturing 1 variable we assign twice by jump:

```julia
julia> function foo()
         # 1 (steps for following control flow)
         closures = []
         count = 0
         # 2, 4
         @label assignx
         @show x = rand()
         if count == 1 @goto next end
         # 3
         push!(closures, () -> x)
         count += 1
         @goto assignx
         # 5
         @label next
         push!(closures, () -> x)
         closures
       end
foo (generic function with 1 method)

julia> result = foo(); dump.(result);
x = rand() = 0.7004247485201015
x = rand() = 0.8141380475376465
#87 (function of type var"#87#89")
  x: Core.Box
    contents: Float64 0.8141380475376465
#88 (function of type var"#88#90")
  x: Core.Box
    contents: Float64 0.8141380475376465

julia> result[1].x === result[2].x # same boxed, captured variable
true

```

But when I rearrange the jump to resemble a while loop, it appears to make separate local variables as if it’s a loop block scope, in other words making an assignment after a jump created a separate captured variable. On the other hand, this variable isn’t isolated to the “loop” body:

```julia
julia> function bar()
         # 1
         closures = []
         count = 0
         # 2, 3
         @label assignx
         @show x = rand()
         push!(closures, () -> x)
         count += 1
         if count == 1 @goto assignx end
         # 4
         closures, x
       end
bar (generic function with 1 method)

julia> result = bar(); dump.(result);
x = rand() = 0.1924894128912077
x = rand() = 0.6303097583847382
Array{Any}((2,))
  1: #27 (function of type var"#27#28"{Float64})
    x: Float64 0.1924894128912077
  2: #27 (function of type var"#27#28"{Float64})
    x: Float64 0.6303097583847382
Float64 0.6303097583847382

```

Not sure if it matters, but this exercise was an offshoot of finding out that `@label` (even with no `@goto`) causes [boxing of captured variables](https://discourse.julialang.org/t/surprising-slowdown-from-using-array-comprehension/129583/6) even in the absence of reassignments. All ran in v1.11.5.

---

<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: [June 4, 2025, 2:58pm UTC](https://discourse.julialang.org/t/does-label-goto-introduce-scope-like-loop-blocks/129607/2 "2025-06-04T14:58:36Z")

</div>

This makes some level of perverse sense, since loops do literally just lower down to `goto` and `label` statements:

```julia-repl
julia> code_lowered(()) do
           count = 0
           while true
               count += 1
           end
           count
       end
1-element Vector{Core.CodeInfo}:
 CodeInfo(
1 ─ count = 0
2 ┄ goto #4 if not true
3 ─ %3 = Main.:+
│ %4 = count
│ count = (%3)(%4, 1)
└── goto #2
4 ─ %7 = count
└── return %7
)

```

```julia-repl
julia> code_lowered(()) do
           count = 0
           @label loopstart
           count += 1
           @goto loopstart
           count
       end
1-element Vector{Core.CodeInfo}:
 CodeInfo(
1 ─ count = 0
2 ┄ %2 = Main.:+
│ %3 = count
│ count = (%2)(%3, 1)
│ nothing
└── goto #2
3 ─ %7 = count
└── return %7
)

```

Notice the only difference between those two forms is a junk `goto #4 if not true` in the loop case and a dangling `nothing`.

The compiler has to later on ‘rediscover’ the presence of loops in order to do various things, so I’m guessing you just tricked it into thinking a loop existed.

---

<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: [June 4, 2025, 6:18pm UTC](https://discourse.julialang.org/t/does-label-goto-introduce-scope-like-loop-blocks/129607/3 "2025-06-04T18:18:10Z")

</div>

> [@Mason](#):
>
> I’m guessing you just tricked it into thinking a loop existed.

Maybe in a sense of whatever criteria it has for distinguishing variables of the same name, as there’s no equivalent Julia loop for either function. `foo` doesn’t behave like one already, but the second closure is instantiated outside its `assignx` area where `x` is assigned, which would be impossible across a block introducing local scope. `bar` allows access to `x` after its `assignx` area (amended example to demonstrate), which would be impossible for the same reason.

```julia
julia> let
         while true
           x = rand()
           break
         end
         x # no local x here
       end
ERROR: UndefVarError: `x` not defined in `Main`

```

Even besides that unsettling half-measure of `bar`’s scoping of `x`, `@label` and `@goto` don’t form a neat block to even suggest a local scope so this seems bad on a semantic level.

---

<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: [June 4, 2025, 6:26pm UTC](https://discourse.julialang.org/t/does-label-goto-introduce-scope-like-loop-blocks/129607/4 "2025-06-04T18:26:59Z")

</div>

I guess @c42f, author of the in-development package JuliaLowering.jl, might have an opinion?

---

<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: [August 24, 2025, 12:45pm UTC](https://discourse.julialang.org/t/does-label-goto-introduce-scope-like-loop-blocks/129607/5 "2025-08-24T12:45:41Z")

</div>

A PR that works around an interesting lowering issue that perhaps arises due to use of GOTO:

- [work around some closure capture boxing inference issues in loading by nsajko · Pull Request #59367 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/pull/59367)

---

<div class="post-metadata">

### Author: ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)
#### Post date: [August 24, 2025, 4:17pm UTC](https://discourse.julialang.org/t/does-label-goto-introduce-scope-like-loop-blocks/129607/6 "2025-08-24T16:17:45Z")

</div>

This is a bug. Correct behavior of `bar` needs to box `x`. 1.1.0 and 1.1.1 still got it right, 1.2.0 got it wrong.

I vaguely remember that Jeff did some modifications to the “closure optimization” stuff back then; that’s where the bug must have been introduced. Somebody should bisect the issue.

(this is in the lisp lowering code)
