# Type annotating captured variables

**URL:** https://discourse.julialang.org/t/type-annotating-captured-variables/99007
**Category:** New to Julia
**Tags:** question
**Created:** [May 17, 2023, 4:57pm UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007 "2023-05-17T16:57:39Z")
**Posts on this page:** 13
**Page:** 1

<div class="post-metadata">

### Author: ![zalky](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zalky/32/49731_2.png) [@zalky](https://discourse.julialang.org/u/zalky)
#### Post date: [May 17, 2023, 4:57pm UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007/1 "2023-05-17T16:57:39Z")

</div>

Hi all,

In the performance tips section of the officials docs, when it comes to captured variables it suggests that:

> … if it is known that a captured variable does not change its type, then this can be declared explicitly with a type annotation.

And they give the following example:

```julia
function abmult2(r0::Int)
    r::Int = r0
    if r < 0
        r = -r
    end
    f = x -> x * r
    return f
end

```

But here the function argument is already type annotated, and according to the docs:

> Function arguments themselves act as new variable _bindings_ (new “names” that can refer to values), much like [assignments](https://docs.julialang.org/en/v1/manual/variables/#man-assignment-expressions) `argument_name = argument_value`

Which would suggest that the second binding is redundant. Why would the captured variable need to be rebound if it is already type annotated in the outer function definition?

Thanks!

---

<div class="post-metadata">

### Author: ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)
#### Post date: [May 17, 2023, 5:09pm UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007/2 "2023-05-17T17:09:41Z")

</div>

Neither of these type annotations are needed for performance. That section is specifically talking about captured variables in closures.

---

<div class="post-metadata">

### Author: ![zalky](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zalky/32/49731_2.png) [@zalky](https://discourse.julialang.org/u/zalky)
#### Post date: [May 17, 2023, 6:26pm UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007/3 "2023-05-17T18:26:33Z")

</div>

Apologies if I’ve misunderstood your response, but I’m not sure how to square it with what the docs say:

> The type annotation partially recovers lost performance due to capturing because the parser can associate a concrete type to the object in the box.

Do you have more detail? Or are you suggesting the docs are incorrect, or outdated?

Also:

> That section is specifically talking about captured variables in closures.

Yes, this was the context of my question.

---

<div class="post-metadata">

### Author: ![johnmyleswhite](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/johnmyleswhite/32/31_2.png) [@johnmyleswhite](https://discourse.julialang.org/u/johnmyleswhite)
#### Post date: [May 17, 2023, 6:45pm UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007/4 "2023-05-17T18:45:47Z")

</div>

I think the docs may be slightly confusing here because the outer type annotation is not relevant, which you can check for yourself:

```julia
julia> function a(r0::Int)
           r::Int = r0
           if r < 0
               r = -r
           end
           f = x -> x * r
           return f
       end
a (generic function with 1 method)

julia> function b(r0)
           r::Int = r0
           if r < 0
               r = -r
           end
           f = x -> x * r
           return f
       end
b (generic function with 1 method)

julia> function c(r0::Int)
           r = r0
           if r < 0
               r = -r
           end
           f = x -> x * r
           return f
       end
c (generic function with 1 method)

julia> f_a = a(1)
#3 (generic function with 1 method)

julia> f_b = b(1)
#5 (generic function with 1 method)

julia> f_c = c(1)
#7 (generic function with 1 method)

julia> using BenchmarkTools

julia> @btime f_a(1)
  9.009 ns (0 allocations: 0 bytes)
1

julia> @btime f_b(1)
  9.425 ns (0 allocations: 0 bytes)
1

julia> @btime f_c(1)
  18.932 ns (0 allocations: 0 bytes)
1

julia> @btime f_a(1)
  8.967 ns (0 allocations: 0 bytes)
1

julia> @btime f_b(1)
  9.384 ns (0 allocations: 0 bytes)
1

julia> @btime f_c(1)
  18.328 ns (0 allocations: 0 bytes)
1

```

---

<div class="post-metadata">

### Author: ![zalky](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zalky/32/49731_2.png) [@zalky](https://discourse.julialang.org/u/zalky)
#### Post date: [May 17, 2023, 7:42pm UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007/5 "2023-05-17T19:42:34Z")

</div>

Thanks this was very helpful and cleared up an ambiguity.

Though docs state that “function arguments themselves act as new variable… much like assignments” thinking on it a bit more, clearly the annotations themselves are a bit different. Assignment annotations (on the left) perform conversion, whereas argument annotations do not (presumably for dispatch). Maybe there’s other differences?

Still, I’m a little surprised that the argument annotation is ignored by the compiler and a separate assignment annotation is required. I’m sure there’s probably a reason.

---

<div class="post-metadata">

### Author: ![johnmyleswhite](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/johnmyleswhite/32/31_2.png) [@johnmyleswhite](https://discourse.julialang.org/u/johnmyleswhite)
#### Post date: [May 17, 2023, 7:58pm UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007/6 "2023-05-17T19:58:24Z")

</div>

> [@zalky](#):
>
> Though docs state that “function arguments themselves act as new variable… much like assignments” thinking on it a bit more, clearly the annotations themselves are a bit different. Assignment annotations (on the left) perform conversion, whereas argument annotations do not (presumably for dispatch). Maybe there’s others?
> 
> Still, I’m a little surprised that the argument annotation is ignored by the compiler and a separate assignment annotation is required. I’m sure there’s probably a reason.

I think you’re over-interpreting the manual a bit.

1. “Function arguments act as new variables” is just an assertion that a function argument is a binding like any other. That doesn’t per se interact with type information.
2. You are right that type annotations inside the body of a function behave differently from those on function arguments – dispatch is the biggest difference and others should be implied by it. But this doesn’t contradict (1) unless you interpret (1) as meaning “function arguments are exactly assignment expressions”, which is definitely false.
3. This example doesn’t provide much useful evidence about what the compiler does with assignment annotations because there’s two different identifiers: `r0` and `r` and the important issues are all about the type information known about `r`.

---

<div class="post-metadata">

### Author: ![zalky](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zalky/32/49731_2.png) [@zalky](https://discourse.julialang.org/u/zalky)
#### Post date: [May 17, 2023, 8:20pm UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007/7 "2023-05-17T20:20:35Z")

</div>

> [@johnmyleswhite](#):
>
> I think you’re over-interpreting the manual a bit.

Yes, I think I was, your response helped.

> [@johnmyleswhite](#):
>
> 1. This example doesn’t provide much useful evidence about what the compiler does with assignment annotations because there’s two different identifiers: `r0` and `r` and the important issues are all about the type information known about `r`.

The only thing here is that the original function in the docs, which they later modify, did not have two different identifiers. There was only one `r` with a type annotation that is apparently ignored.

```julia
function abmult(r::Int)
    if r < 0
        r = -r
    end
    f = x -> x * r
    return f
end

```

Any idea why the compiler would ignore that as useful information about the type of `r`?

---

<div class="post-metadata">

### Author: ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)
#### Post date: [May 17, 2023, 8:26pm UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007/8 "2023-05-17T20:26:59Z")

</div>

It already knows the information without you telling it. Julia specializes functions on the types of inputs, so when you type `abmult(4)` it compiles a special version for `abmult(::Int)` whether you tell it the input is an `int` or not.

---

<div class="post-metadata">

### Author: ![zalky](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zalky/32/49731_2.png) [@zalky](https://discourse.julialang.org/u/zalky)
#### Post date: [May 17, 2023, 8:39pm UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007/9 "2023-05-17T20:39:18Z")

</div>

If it knows the information already then why are the docs telling us to add more annotations:

```julia
r::Int = r0

```

---

<div class="post-metadata">

### Author: ![johnmyleswhite](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/johnmyleswhite/32/31_2.png) [@johnmyleswhite](https://discourse.julialang.org/u/johnmyleswhite)
#### Post date: [May 17, 2023, 8:53pm UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007/10 "2023-05-17T20:53:30Z")

</div>

I think this example is misleading you because so many different issues are in play.

1. `r` and `r0` are two different variables. Properties of one don’t necessarily apply to the other. So there’s no tension between Julia’s behavior being influenced by annotations on `r` – that is orthogonal to annotations on `r0`.
2. Both `r` and `r0` are mutable bindings – in particular, because Julia is a dynamic language, both of them could be rebound to values of a new type. Examples will come below to clarify this.
3. The actual invariant that Julia’s compiler knows is that when the function body is entered, the type of the value bound to `r0` is known exactly. This type is not necessarily invariant during the rest of the body.

Example 1: `r0` is rebound during the function body to a new type.

```julia
julia> function f1(r0::Int)
           println(typeof(r0))
           r0 = 1.2
           println(typeof(r0))
       end
f1 (generic function with 1 method)

julia> f1(123)
Int64
Float64

```

Example 2: `r` is rebound during the function body to a new type.

```julia
julia> function f2(r0::Int)
           r = r0
           println(typeof(r))
           r = 1.2
           println(typeof(r))
       end
f2 (generic function with 1 method)

julia> f2(123)
Int64
Float64

```

---

<div class="post-metadata">

### Author: ![zalky](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zalky/32/49731_2.png) [@zalky](https://discourse.julialang.org/u/zalky)
#### Post date: [May 17, 2023, 9:45pm UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007/11 "2023-05-17T21:45:16Z")

</div>

Thanks @johnmyleswhite for taking the time to explain this in detail. It helped a number of things finally fall into place!

**To recap** : annotating the function argument does not guarantee type stability of the binding throughout the rest of the body. Whereas the annotation on the assignment, does.

---

<div class="post-metadata">

### Author: ![zalky](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zalky/32/49731_2.png) [@zalky](https://discourse.julialang.org/u/zalky)
#### Post date: [May 17, 2023, 10:07pm UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007/12 "2023-05-17T22:07:52Z")

</div>

And an example of how the assignment annotation enforces stability:

```julia
function f(r0::Int64)
    r::Int64 = r0
    r = Int8(r)
    println(typeof(r))
end

julia> f(1)
Int64

```

---

<div class="post-metadata">

### Author: ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)
#### Post date: [May 18, 2023, 4:12am UTC](https://discourse.julialang.org/t/type-annotating-captured-variables/99007/13 "2023-05-18T04:12:01Z")

</div>

I think if you have an assignment with a type assert like `r::Int = 1` then Julia simply edits a conversion and type assert into every other assignment to `r` in the function. Like the next `r = something` statement will become something like (pseudocode) `r = assert(convert(Int, something) isa Int)`. And those should compile away if the compiler can prove that the new assignments are going to be `Int` anyway. Maybe one can see this with `@code_lowered`.
