When I start one of my functions in the debugger, I see in the Workspace that my “ct” variable shows up like this – before it is even referenced:
After the variable “ct” is assigned, it shows up like this in the workspace:
What is the meaning of Core.Box here? I tried searching the docs and a web search but didn’t turn up anything.
“Boxing” is wrapping a value of an unknown (at compile time) concrete type into some language-defined standard container which the runtime can then unpack to get type and value. Core.Box
is the datatype for such containers in Julia.
In Julia, boxing usually happens when a variable is not type-stable.
While true, the specific case of Core.Box
happens when a closure captures a variable and mutates it:
julia> function g()
x = 1
h() = x = 2.0
h()
return x
end
g (generic function with 1 method)
julia> @code_warntype g()
Variables
#self#::Core.Compiler.Const(g, false)
x@_2::Core.Box <---------------------------
h::var"#h#4"
x@_4::Union{}
Body::Any
1 ─ (x@_2 = Core.Box())
│ Core.setfield!(x@_2, :contents, 1)
│ (h = %new(Main.:(var"#h#4"), x@_2))
│ (h)()
│ %5 = Core.isdefined(x@_2, :contents)::Bool
└── goto #3 if not %5
2 ─ goto #4
3 ─ Core.NewvarNode(:(x@_4))
└── x@_4
4 ┄ %10 = Core.getfield(x@_2, :contents)::Any
└── return %10
This is because assignments in Julia are always no-ops but the semantics of a closure is that it should mutate the variables it captures.
The frontend therefore introduces a box with some gensymmed name (here x@_2
) and rewrites all writes to x
(for example x = val
) as Core.setfield!(x@_2, :contents, val)
and all access to x
as Core.getfield(x@_2, :contents)
. The Core.Box
is a mutable container so changes to it can be observed even if those changes happens in another function (here, the closure).
The debugger looks at values in this lowered representation which is why it sees the Box
. However, that is arguably an implementation detail leaking so perhaps the Juno debugger should just show the .contents
field of the Box
.
4 Likes