Is there a specific reason why the const keyword cannot be used within the scope of a function?

I noticed that trying to use the const keyword in a local scope results in a compilation error. Is there a specific reason for this? I wanted to define an anonymous function in a local scope (see Creating an anonymous function at the local level. Conflict with const resulting in performance issues - #5 by stefkuypers for details) since it was an elegant solution to a problem I was facing but the performance hit I got due to the fact that I could not make that function a const made it useless. So now I’m curious as to why the use of the const keyword is not allowed in a local scope.

No, it could be done.

It actually used to be ignored before v1, and it was made into an error to allow reintroduction without breaking code. It’s not here yet because they haven’t figured out how it can work and why it should work. For example,

this is not true. Only variables can be const, not objects like functions. You already do not reassign the local variable sim_function in the method init_model, the only thing that const would enforce, and const-ness makes zero difference when passed into a method call Model(sim_function). There are likely other reasons for the performance hit, the most immediate one is the inherent type instability of struct Model sim_function::Function end, which foobar_lv2 has already explained.

The following code clearly proves that functions can be made const too.

using BenchmarkTools

foo1 = x -> x + 2
const foo2 = x -> x + 2

@btime for i in 1:10000; foo1(i); end

@btime for i in 1:10000; foo2(i); end

On my computer, the first execution results in the following output:
143.292 ÎĽs (18980 allocations: 296.56 KiB)

The second execution results in:
0.916 ns (0 allocations: 0 bytes)

I’d say that is quite a performance hit for not using the const keyword with an anonymous function.

As for the remark on the Function type, when I use a named function instead of an non const anonymous function I don’t have the performance penalty.

No it doesn’t, you’re using BenchmarkTools wrong and misinterpreting the outcome. If we follow its documentation on treating non-const global variables:

julia> using BenchmarkTools

julia> foo1 = x -> x + 2
#1 (generic function with 1 method)

julia> const foo2 = x -> x + 2
#3 (generic function with 1 method)

julia> @btime for i in 1:10000; $foo1(i); end
  1.700 ns (0 allocations: 0 bytes)

julia> @btime for i in 1:10000; $foo2(i); end
  2.900 ns (0 allocations: 0 bytes)

All those allocations are gone, in fact the “const” foo2 ran a bit slower (the difference is just within OS randomness). Now let’s flip it around with the incorrect usage:

julia> const bar1 = foo1
#1 (generic function with 1 method)

julia> bar2 = foo2
#3 (generic function with 1 method)

julia> @btime for i in 1:10000; bar1(i); end
  1.300 ns (0 allocations: 0 bytes)

julia> @btime for i in 1:10000; bar2(i); end
  293.300 ÎĽs (18980 allocations: 296.56 KiB)

julia> bar1 === foo1, bar2 === foo2
(true, true)

Now the non-const bar2 took a performance hit, but it is assigned to the exact same function as const foo2. const-ness is about variables, not functions, and there’s a massive difference between variables being shared across scopes and functions being passed as arguments.

Named functions inside local scopes aren’t const and neither are the names, as you can easily reassign them without erroring.

1 Like

Ok, thanks. I seriously need to wrap my head around this!

Why is bar2 taking a performance hit now???

Ok, hold on. bar1 and bar2 are variables, right? And thus in the bar example bar1 is the const variable and bar2 is the non-const variable. Ok, I think I get it now.