Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala - How is val immutability guaranteed at run time

Tags:

java

jvm

scala

When we create a final in java it is guaranteed that it cannot be changed even at run time because the JVM guarantees it.

Java class:

public class JustATest {
    public final int x = 10;
}

Javap decompiled:

Compiled from "JustATest.java"

public class JustATest {
  public final int x;

  public JustATest();
    Code:
       0: aload_0
       1: invokespecial #1                // Method java/lang/Object."<init>":()V
       4: aload_0
       5: bipush        10
       7: putfield      #2                  // Field x:I
      10: return
}

But in scala, if we declare a val, it compiles into a normal integer and there is no difference between var and val in terms of decompilation output.

Original Scala class:

class AnTest {

  val x = 1
  var y = 2
}

Decompiled output:

Compiled from "AnTest.scala"
public class AnTest {
  public int x();
    Code:
       0: aload_0
       1: getfield      #14                 // Field x:I
       4: ireturn

  public int y();
    Code:
       0: aload_0
       1: getfield      #18                 // Field y:I
       4: ireturn

  public void y_$eq(int);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      #18                 // Field y:I
       5: return

  public AnTest();
    Code:
       0: aload_0
       1: invokespecial #25                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_1
       6: putfield      #14                 // Field x:I
       9: aload_0
      10: iconst_2
      11: putfield      #18                 // Field y:I
      14: return
}

With that information, the concept of immutability of a val is controlled only at compile time by the scala compiler? How is this guaranteed at run time?

like image 816
Greedy Coder Avatar asked Dec 14 '22 03:12

Greedy Coder


2 Answers

In Scala, conveying immutability via val is a compile time enforcement which has nothing to do with the emitted byte code. In Java, you state that when the field is final in order for it not to be reassigned, where in Scala, declaring a variable with val only means it can't be reassigned, but it can be overridden. If you want a field to be final, you'll need to specify it as you do in Java:

class AnTest {
  final val x = 10
}

Which yields:

public class testing.ReadingFile$AnTest$1 {
  private final int x;

  public final int x();
    Code:
       0: bipush        10
       2: ireturn

  public testing.ReadingFile$AnTest$1();
    Code:
       0: aload_0
       1: invokespecial #19                 // Method java/lang/Object."<init>":()V
       4: return
}

Which is equivalent to the byte code you see in Java, except the compiler has emitted a getter for x.

like image 186
Yuval Itzchakov Avatar answered Dec 21 '22 10:12

Yuval Itzchakov


The really simple answer is: there are some Scala features which can be encoded in JVM bytecode, and some which can't.

In particular, there are some constraints which cannot be encoded in JVM bytecode, e.g. sealed or private[this], or val. Which means that if you get your hands on the compiled JVM bytecode of a Scala source file, then you can do stuff that you can't do from Scala by interacting with the code through a language that is not Scala.

This is not specific to the JVM backend, you have similar, and even more pronounced problems with Scala.js, since the compilation target here (ECMAScript) offers even less ways of expressing constraints than JVM bytecode does.

But really, this is just a general problem: I can take a language as safe and pure as Haskell, compile it to native code, and if I get my hands on the compiled binary, all safety will be lost. In fact, most Haskell compilers perform (almost) complete type erasure, so there are literally no types, and no type constraints left after compilation.

like image 25
Jörg W Mittag Avatar answered Dec 21 '22 11:12

Jörg W Mittag