Open
Description
Basic case
It's reasonable to expect _, _ = ...
to evaluate the right hand side before
setting the variables on the left. Some cases follow this intuition, for
example the swapping idiom:
julia> x = 1
y = 2
x, y = y, x
(2, 1)
Other cases, including those with a ref
on the LHS, also work:
julia> x = Ref(1)
x[], y = 2, x[]
(2, 1)
However, others don't:
julia> x = 1
f() = x
x, y = 2, f()
(2, 2)
The lowering for x, y = 2, x
:
1 ─ %1 = 2
│ %2 = Main.x
│ $(Expr(:globaldecl, :(Main.x)))
│ $(Expr(:latestworld))
│ %5 = builtin Core.get_binding_type(Main, :x)
│ #s1 = %1
│ %7 = #s1
│ %8 = dynamic %7 isa %5
└── goto #3 if not %8
2 ─ goto #4
3 ─ %11 = #s1
└── #s1 = Base.convert(%5, %11)
4 ┄ %13 = #s1
│ dynamic Base.setglobal!(Main, :x, %13)
│ $(Expr(:globaldecl, :(Main.y)))
│ $(Expr(:latestworld))
│ %17 = builtin Core.get_binding_type(Main, :y)
│ @_2 = %2
│ %19 = @_2
│ %20 = dynamic %19 isa %17
└── goto #6 if not %20
5 ─ goto #7
6 ─ %23 = @_2
└── @_2 = Base.convert(%17, %23)
7 ┄ %25 = @_2
│ dynamic Base.setglobal!(Main, :y, %25)
│ %27 = dynamic Core.tuple(%1, %2)
└── return %27
The lowering for x, y = 2, f()
:
1 ─ $(Expr(:globaldecl, :(Main.x)))
│ $(Expr(:latestworld))
│ %3 = builtin Core.get_binding_type(Main, :x)
│ #s2 = 2
│ %5 = #s2
│ %6 = dynamic %5 isa %3
└── goto #3 if not %6
2 ─ goto #4
3 ─ %9 = #s2
└── #s2 = Base.convert(%3, %9)
4 ┄ %11 = #s2
│ dynamic Base.setglobal!(Main, :x, %11)
│ %13 = Main.f
│ %14 = dynamic (%13)()
│ $(Expr(:globaldecl, :(Main.y)))
│ $(Expr(:latestworld))
│ %17 = builtin Core.get_binding_type(Main, :y)
│ @_2 = %14
│ %19 = @_2
│ %20 = dynamic %19 isa %17
└── goto #6 if not %20
5 ─ goto #7
6 ─ %23 = @_2
└── @_2 = Base.convert(%17, %23)
7 ┄ %25 = @_2
│ dynamic Base.setglobal!(Main, :y, %25)
│ %27 = dynamic Core.tuple(2, %14)
└── return %27
Multiple const
assignments
In 1.12 we have the related issue of constants becoming visible mid-evaluation
because of a latestworld
. For example, in const x, y = 2, f()
:
1 ─ %1 = 2
│ x = %1
│ $(Expr(:const, :(Main.x), :(x)))
│ $(Expr(:latestworld))
│ %5 = Main.f
│ %6 = dynamic (%5)()
│ y = %6
│ $(Expr(:const, :(Main.y), :(y)))
│ $(Expr(:latestworld))
│ %10 = dynamic Core.tuple(2, %6)
└── return %10
Typeasserts
Typeasserts present yet another problem; convert
can fail and leave only some
of the variables assigned:
julia> x = 0
y = "a"
z = 0
0
julia> x, y::String, z::Int = 1, 2, 3
ERROR: cannot set type for global Main.y. It already has a value or is already set to a different type.
Stacktrace:
[1] top-level scope
@ REPL[3]:1
julia> x, y, z
(1, "a", 0)
Lowered:
1 ── $(Expr(:globaldecl, :(Main.x)))
│ $(Expr(:latestworld))
│ %3 = builtin Core.get_binding_type(Main, :x)
│ #s3 = 1
│ %5 = #s3
│ %6 = dynamic %5 isa %3
└─── goto #3 if not %6
2 ── goto #4
3 ── %9 = #s3
└─── #s3 = Base.convert(%3, %9)
4 ┄─ %11 = #s3
│ dynamic Base.setglobal!(Main, :x, %11)
│ %13 = 2
│ %14 = 3
│ %15 = Main.String
│ $(Expr(:globaldecl, :(Main.y), :(%15)))
│ $(Expr(:latestworld))
│ $(Expr(:globaldecl, :(Main.y)))
│ $(Expr(:latestworld))
│ %20 = builtin Core.get_binding_type(Main, :y)
│ @_2 = %13
│ %22 = @_2
│ %23 = dynamic %22 isa %20
└─── goto #6 if not %23
5 ── goto #7
6 ── %26 = @_2
└─── @_2 = Base.convert(%20, %26)
7 ┄─ %28 = @_2
│ dynamic Base.setglobal!(Main, :y, %28)
│ %30 = Main.Int
│ $(Expr(:globaldecl, :(Main.z), :(%30)))
│ $(Expr(:latestworld))
│ $(Expr(:globaldecl, :(Main.z)))
│ $(Expr(:latestworld))
│ %35 = builtin Core.get_binding_type(Main, :z)
│ @_3 = %14
│ %37 = @_3
│ %38 = dynamic %37 isa %35
└─── goto #9 if not %38
8 ── goto #10
9 ── %41 = @_3
└─── @_3 = Base.convert(%35, %41)
10 ┄ %43 = @_3
│ dynamic Base.setglobal!(Main, :z, %43)
│ %45 = dynamic Core.tuple(1, %13, %14)
└─── return %45