scala/ハマりどころ
ドキュメントにコンパイラオプションが網羅されていないがち
objectではvalのforward referenceができてしまう
以下のようなコードについて考える。
code:scala
def f =
val x = y // y is a forward reference extending over the definition of x
val y = 42
println(x)
当然これはコンパイルが通らない。xの定義のタイミングでyは存在しないため。
しかしobjectだとこれができてしまう:
code:scala
object O:
val x = y
val y = 42
O.x // => 0
プリミティブ型ではないときはnullになるので、ぬるぽの原因になる
Note that the value defined by an object definition is instantiated lazily. The new m$cls constructor is evaluated not at the point of the object definition, but is instead evaluated the first time m is dereferenced during execution of the program (which might be never at all). An attempt to dereference m again during evaluation of the constructor will lead to an infinite loop or run-time error. Other threads trying to dereference m while the constructor is being evaluated block until evaluation is complete.
(機械翻訳)
オブジェクト定義によって定義された値は、遅延初期化される点に注意してください。新しい m$cls コンストラクタは、オブジェクト定義の時点では評価されず、プログラム実行時に m が初めて参照された時点で初めて評価されます(場合によっては全く評価されないこともあります)。コンストラクタ評価中に再度 m を参照しようとすると、無限ループや実行時エラーが発生します。コンストラクタ評価中に、他のスレッドが m を参照しようとした場合、評価が完了するまで待機状態になります。
disasmした結果を見るとわかるが、
objectのフィールドはJVMレベルではprivate static finalのフィールドとして定義される
初期状態では、プリミティブ型は0やfalseで初期化される
これはJavaの仕様
で、x=yという初期化がさらに行われる
yはまだ0扱いなのでx=0になる
次にy=42になる
回避策
コンパイラオプションに-Wsafe-initをつける(Scala 3の場合)と、コンパイラが警告するようになる
code:scala
warn Access non-initialized value y. Calling trace: どっちかをlazy valにすると後から初期化されるようになるので値が正常になる
----
このコードをdisasmすると:
code:java
public final class O {
public static int x();
0: getstatic #13 // Field O$.MODULE$:LO$; 3: invokevirtual #15 // Method O$.x:()I 6: ireturn
public final class O$ implements java.io.Serializable {
private static final int x;
private static final int y;
public static final O$ MODULE$;
private O$();
0: aload_0
1: invokespecial #16 // Method java/lang/Object."<init>":()V 4: return
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LO$;
public static {};
3: dup
4: invokespecial #19 // Method "<init>":()V 7: putstatic #21 // Field MODULE$:LO$; 10: getstatic #21 // Field MODULE$:LO$; 13: invokevirtual #24 // Method y:()I 16: putstatic #26 // Field x:I 19: bipush 42
21: putstatic #28 // Field y:I 24: return
private java.lang.Object writeReplace();
0: new #32 // class scala/runtime/ModuleSerializationProxy 3: dup
6: invokespecial #35 // Method scala/runtime/ModuleSerializationProxy."<init>":(Ljava/lang/Class;)V 9: areturn
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this LO$;
public int x();
0: getstatic #26 // Field x:I 3: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this LO$;
public int y();
0: getstatic #28 // Field y:I 3: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this LO$;
}