# Is it possible to know if a function has captured local variables?

**URL:** <https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934>\
**Category:** General Usage\
**Tags:** function\
**Created:** [May 12, 2025, 9:18pm UTC](https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934 "2025-05-12T21:18:49Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [May 12, 2025, 9:18pm UTC](https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934/1 "2025-05-12T21:18:49Z")

</div>

```julia
julia> a = fill(1)
0-dimensional Array{Int64, 0}:
1

julia> function foo(b)
           a[] + b
       end
foo (generic function with 1 method)

julia> foo(1)
2

julia> a[] = 2
2

julia> foo(1)
3

julia> some_detector(foo) # returns `true`

```

Is it possible to develop a function like `some_detector` to detect that `foo` has a captured variable that is outside the scope of the body of `foo`?

I thought `Base.isbitstype` would be sufficient, but unfortunately that’s not the case:

```julia
julia> function useless_detector(::F) where {F<:Function}
           !isbitstype(F)
       end
useless_detector (generic function with 1 method)

julia> useless_detector(foo)
false

```

A related discovery I had is that even if I perform a `deepcopy` on `foo`, I still cannot isolate `a`:

```julia
julia> foo_dc = deepcopy(foo)
foo (generic function with 1 method)

julia> a[] += 1
3

julia> foo_dc(1) == foo(1)
true

```

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [May 12, 2025, 9:48pm UTC](https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934/2 "2025-05-12T21:48:29Z")

</div>

> [@frankwswang](#):
>
> Is it possible to develop a function like `some_detector` to detect that `foo` has a captured variable that is outside the scope of the body of `foo`?

In general you can’t do that for a function, but maybe for a method. I’m not sure how to do it reliably, but there is some information on captured variables in `methods(foo)[1].roots`. It’s not documented, so no guarantees about anything.

---

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [May 12, 2025, 10:15pm UTC](https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934/3 "2025-05-12T22:15:42Z")

</div>

Also, I found it concerning that

```julia
julia> ismutable(foo)
false

```

because `foo` is technically mutable through modifying `a`, and it’s not like `String` that is intended to be treated like an immutable object, since closure is fully supported and even documented in [Julia’s official documentation](https://docs.julialang.org/en/v1/devdocs/functions/#Closures).

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 12, 2025, 10:30pm UTC](https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934/4 "2025-05-12T22:30:35Z")

</div>

> [@frankwswang](#):
>
> because `foo` is technically mutable through modifying `a`,

That’s not what immutable means.

```julia
struct Foo
    x::Array
end

```

is immutable even though you can modify the array that it points to:

```julia
julia> foo = Foo([1,2,3])
Foo([1, 2, 3])

julia> foo.x[1] = 7
7

julia> foo
Foo([7, 2, 3])

```

Immutability is about what the compiler is allowed to do with a `Foo` object, not a promise that nothing _referenced_ by `foo` can change.

It analogous to

```julia
const a = [1,2,3]

```

being `const`, even though the _contents_ of `a` can be changed.

---

<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:** [May 12, 2025, 10:39pm UTC](https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934/5 "2025-05-12T22:39:55Z")

</div>

That’s not capture by a closure, just accessing a global variable, which is a lot harder to figure out from the implementation. It seems like the `Method` instance’s `roots` field holds global variables, ~~not sure though.~~ but it holds many other things and I have no idea how to test for attempted global variable accesses when the global variable hasn’t been defined yet.

---

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [May 12, 2025, 10:55pm UTC](https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934/6 "2025-05-12T22:55:44Z")

</div>

Thanks for the reply. I understand that an immutable composite type instance can hold a mutable field, which I assume is what you are referring to. However, I was unsure if that is what `ismutable` is trying to do.

Note that your analogy to a global constant variable does not apply to the behavior of `ismutable`:

```julia
julia> const v1 = [1,2,3]
3-element Vector{Int64}:
 1
 2
 3

julia> ismutable(v1)
true

```

This also holds for the latest `Memory` type:

```julia
julia> const v2 = Memory{Int}([1,2,3])
3-element Memory{Int64}:
 1
 2
 3

julia> ismutable(v2)
true

```

If for a composite-type instance, `ismutable` just checks if it is constructed by the keyword `struct`, that’s fine then.

On a related note, the documentation for [immutability of composite types](https://docs.julialang.org/en/v1/manual/types/#Mutable-Composite-Types) seems misleading/imprecise:

> To recap, two essential properties define immutability in Julia:
> 
> - It is not permitted to modify the value of an immutable type.

“value” seems a bit too strong, as for a mutable field in immutable structs, it’s just the reference (pointer) that remains immutable.

---

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [May 12, 2025, 11:23pm UTC](https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934/7 "2025-05-12T23:23:04Z")

</div>

> [@Benny](#):
>
> That’s not capture by a closure, just accessing a global variable, which is a lot harder to figure out from the implementation.

Thanks for pointing that out! I thought a closure would behave the same as a function capturing a global variable. But it seems not to be the case:

```julia
julia> function f2(x::Array{Int, 0})
           f = function (c)
               x[] + c
           end
       end
f2 (generic function with 1 method)

julia> c = fill(1)
0-dimensional Array{Int64, 0}:
1

julia> f2c = f2(c)
#1 (generic function with 1 method)

julia> f2c(1)
2

julia> c[] += 1
2

julia> f2c(1)
3

julia> f2c_dc = deepcopy(f2c)
#1 (generic function with 1 method)

julia> f2c_dc(1)
3

julia> c[] += 1
3

julia> f2c(1)
4

julia> f2c_dc(1)
3

```

Is there a reason why a global variable captured in a function will not be affected by `deepcopy` but will be affected when it is passed to a closure?

---

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [May 12, 2025, 11:31pm UTC](https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934/8 "2025-05-12T23:31:32Z")

</div>

Okay, it seems that it’s not about whether the constructed function is a closure, but about how the global variable is embedded in the function body, which is even more confusing now:

```julia
julia> d = fill(1)
0-dimensional Array{Int64, 0}:
1

julia> function f3()
           function (y)
           d[] + y
           end
       end
f3 (generic function with 1 method)

julia> f3c = f3()
#3 (generic function with 1 method)

julia> f3c(1)
2

julia> d[] += 1
2

julia> f3c(1)
3

julia> f3c_dc = deepcopy(f3c)
#3 (generic function with 1 method)

julia> f3c_dc(1)
3

julia> d[] += 1
3

julia> f3c_dc(1)
4

julia> f3c(1)
4

```

---

<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:** [May 13, 2025, 12:12am UTC](https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934/9 "2025-05-13T00:12:57Z")

</div>

> [@frankwswang](#):
>
> function capturing a global variable

That can’t happen. Capturing is just for local variables with scopes that can end. Global variables just exist whereever they are.

---

<div class="post-metadata">

**Author:** ![danielwe](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielwe/32/35657_2.png) [@danielwe](https://discourse.julialang.org/u/danielwe)\
**Post date:** [May 13, 2025, 12:15am UTC](https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934/10 "2025-05-13T00:15:16Z")

</div>

It’s a subtle distinction at first, but a _closure_ is a narrower concept than simply a function referencing a variable from a parent scope. A closure is a function that does this _after the parent scope has expired_, and therefore needs to carry the variables with it as part of the function object.

This is not a closure:

```julia
a = Ref(1)
f(x) = x + a[]

```

This function doesn’t package anything extra, it’s just a regular function that happens to reference a global variable. Every time this function is called, it will dynamically look up the current value of `a` in the global scope, according to the normal rules of lexical scoping.

This is a closure:

```julia
g = let b = Ref(1)
    x -> x + b[]
end

```

Here, the lifetime of the variable `b` will have expired by the time `g` is assigned, since it was defined in the local scope started by `let` and ended by `end`. Thus, it’s up to `g` to keep `b` alive such that future calls still work. Therefore, a reference to `b` is stored as part of the `g` object. This is what a closure is: a function that comes packaged with one or more bindings from an expired scope. It’s actually just stored as a field. Try it yourself: `g.b[] == 1`. (This is an implementation detail, though, you shouldn’t rely on it.)

In contrast, there is no `f.a`. It’s not needed, since `a` is still alive and well in the global scope, such that `f` can reference it at any time.

This explains your observations. `f2 = deepcopy(f)` just gives you a function identical to `f`. In fact, since functions are immutable singletons, it will return `f` itself, such that `f2 === f`. The variable `a` is not involved, since there’s no reference from the object `f` to `a`.

In contrast, `g2 = deepcopy(g)` will recursively walk the `g` object, notice the reference to `b`, and make a copy of `b` and assemble a new function of the same type as `g` using the new copy of `b`. Thus, `g.b` and `g2.b` are not the same object, so mutating `g.b` does not change the behavior of `g2`, and vice versa.

Hope this helps!

---

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [May 13, 2025, 1:31am UTC](https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934/11 "2025-05-13T01:31:29Z")

</div>

Thanks for the explanation!! It’s very clear!!

---

<div class="post-metadata">

**Author:** ![mcabbott](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mcabbott/32/6603_2.png) [@mcabbott](https://discourse.julialang.org/u/mcabbott)\
**Post date:** [May 13, 2025, 1:40am UTC](https://discourse.julialang.org/t/is-it-possible-to-know-if-a-function-has-captured-local-variables/128934/12 "2025-05-13T01:40:57Z")

</div>

`!issingletontype` will detect many closures, perhaps all although I’m not very sure?

```julia
julia> let a = fill(1)
        foo2(b) = a[] + b
        @show typeof(foo2)
        Base.issingletontype(typeof(foo2))
       end
typeof(foo2) = var"#foo2#41"{Array{Int64, 0}}
false

julia> foo3 = let a2 = fill(2)
         foo3(b) = a2[] + b
       end
foo3 (generic function with 1 method)

julia> typeof(foo3)
var"#foo3#42"{Array{Int64, 0}}

julia> Base.issingletontype(typeof(foo3))
false

```

It does not detect that the original `foo` depends on global `a`:

```julia
julia> typeof(foo)
typeof(foo) (singleton type of function foo, subtype of Function)

julia> Base.issingletontype(typeof(foo))
true

```
