This is not hard evidence from the specs, but it illustrates some things the compiler does for you, and that should allow you to regard a few match blocks as atomic - but definitely not all. It will be much safer if you synchronise your code yourself, or if you go with immutable objects.
Flat example
If you run the following script with scala -print
:
var m: Option[String] = _
m match {
case Some(s) => "Some: " + s
case None => "None"
}
you'll see the desugared intermediate code created by the compiler (I've removed some code for brevity):
final class Main$$anon$1 extends java.lang.Object {
private[this] var m: Option = _;
private <accessor> def m(): Option = Main$$anon$1.this.m;
def this(): anonymous class Main$$anon$1 = {
<synthetic> val temp1: Option = Main$$anon$1.this.m();
if (temp1.$isInstanceOf[Some]()) {
"Some: ".+(temp1.$asInstanceOf[Some]().x())
else if (scala.this.None.==(temp1))
"None"
else
throw new MatchError(temp1)
}
}
The possibly shared object referenced by m
gets a local alias temp1
, so if m
is changed in the background such that it points to another object, then the match still takes place on the old object m
pointed to. Hence, the situation you described above (changing global_point
to point to a TwoDim
instead of to a OneDim
) is not a problem.
Nested example
It seems to generally be the case that the compiler creates local aliases to all objects that are bound in the guard of a match case, but it does not create a deep copy!
For the following script:
case class X(var f: Int, var x: X)
var x = new X(-1, new X(1, null))
x match {
case X(f, ix) if f > 0 || ix.f > 0 => "gt0"
case X(f, ix) if f <= 0 || ix.f <= 0 => "lte0"
}
the compiler creates this intermediate code:
private[this] var x: anonymous class Main$$anon$1$X = _;
private <accessor> def x(): anonymous class Main$$anon$1$X = Main$$anon$1.this.x;
final <synthetic> private[this] def gd2$1(x$1: Int, x$2: anonymous class Main$$anon$1$X): Boolean = x$1.>(0).||(x$2.f().>(0));
final <synthetic> private[this] def gd3$1(x$1: Int, x$2: anonymous class Main$$anon$1$X): Boolean = x$1.<=(0).||(x$2.f().<=(0));
def this(): anonymous class Main$$anon$1 = {
<synthetic> val temp6: anonymous class Main$$anon$1$X = Main$$anon$1.this.x();
if (temp6.ne(null)) {
<synthetic> val temp7: Int = temp6.f();
<synthetic> val temp8: anonymous class Main$$anon$1$X = temp6.x();
if (Main$$anon$1.this.gd2$1(temp7, temp8))
"gt0"
else if (Main$$anon$1.this.gd3$1(temp7, temp8))
"lte0"
else
throw new MatchError(temp6)
} else
throw new MatchError(temp6)
}
Here, the compiler creates local aliases for the object x
you match on, and for its two sub-objects x.f
(bound to f
) and x.x
(bound to ix
), but not for ix.f
. Hence, if the structure you match on is deeply nested and your cases depend on nested objects that you don't bind locally, then race conditions can occur. And will, as we all know, due to Murphy's Law.