I wonder what your preferred ways of initializing a read-only variable using imperative code are. This becomes important, e.g., when there are closures involved and Julia wants to box the variable.

Something like

function g(x) = nothing # or something... who knows
function f(x::Integer, a::Integer)
# Initialize y
y = 1
# let's pretend this sum can't be reformulated to something in closed form
for i in 1:x
y = y + i
end
# From this point on, y is basically constant
# [...]
sum(g(y + b) for b in 1:a)
end

My ideas were

function f(x, a)
y = 1
for i in 1:x
y = y + i
end
let y = y
sum(g(y + b) for b in 1:a)
end
end

and

function f(x::Integer, a::Integer)
y = begin
j = 1
for i in 1:x
j = j + i
end
j
end
sum(g(y + b) for b in 1:a)
end

I believe, those two variants should be equivalent. Although I am not completely certain. Personally I went with the first option, as it felt more concise to me.

What are your preferred ways of preventing a Box there and why?

My preference is to write a specific function for initialization if possible. Out of the two proposed, the second feels â€śbetterâ€ť, only Iâ€™d write it as

y = let y = 1
for i in 1:x
y = y + i
end
y
end

Thereâ€™s also the â€śRef trickâ€ť:

function f(x::Integer, a::Integer)
# Initialize y
y = Ref(1)
for i in 1:x
y[] = y[] + i
end
sum(g(y[] + b) for b in 1:a)
end

Now, there is no binding change for y, so no boxing needed.

Julia compiler is free to decide if a struct (mutable or not) is stack- or heap-allocated. Ref(1) can be stack allocated if the compiler can prove it is safe. My guess is that arrays cannot be stack allocated because they donâ€™t have a fixed size. But heap allocation of a Ref must be cheaper than array, too, because arrays have to store more meta information.

In this case, @btime shows no allocations with Ref for simple initialization procedures, but switches to heap allocation if thereâ€™s something complicated in the loop. So, using one of your initial solutions is a faster way, and both seem equivalent in performance to me.

But Ref is useful if y has to be mutated inside the closure or if you want to share a mutable value between multiple closures.