Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala implicit parameter null when implicit val defined after method call

Tags:

scala

implicit

Consider:

object implicitnull extends App {
  mymethod

  implicit val arg = "foo"

  def mymethod(implicit arg: String) = {
    arg.size
  }
}

This does not cause any compilation error, however, at runtime results in NullPointerException coming from arg.size.

Is this the intended behavior?

like image 717
Erik Kaplun Avatar asked Jan 11 '23 02:01

Erik Kaplun


1 Answers

Yes it's the intended behavior due to the way Scala constructs classes and initializes them. Consider this example:

scala> class A {
     |   f
     | 
     |   implicit val arg = "foo"
     | 
     |   def f(implicit arg: String) = {
     |     println(arg)
     |   }
     | }
defined class A

scala> class B {
     |   f(arg)
     | 
     |   val arg = "foo"
     | 
     |   def f(implicit arg: String) = {
     |     println(arg)
     |   }
     | }
defined class B

scala> class C {
     |   implicit val arg = "foo"
     | 
     |   f
     | 
     |   def f(implicit arg: String) = {
     |     println(arg)
     |   }
     | }
defined class C

scala> new A
null
res0: A = A@67d3caf

scala> new B
null
res1: B = B@3f2c5ad4

scala> new C
foo
res2: C = C@177bdd23

At the moment the function f is invoked in class C the value has been initialized, while in class B it has been not initialized yet. Class A is exactly the same as class B - the only difference is that Scala passes the arg implicitly in A.

It's a little bit confusing because this code is doing 2 things - it's declaring member variables and executing constructor code. If you take class B for example, val arg would be declared at the point where f is called, but not initialized yet. val arg = "foo" does the initialization. Once you translate this to Java it becomes more obvious:

public class B {

    void f(String arg) {
        System.out.println(arg);
    }

    String arg; // also acts as final in Scala

    public B() {
        f(arg);
        arg = "foo";
    }
}

Using lazy val or correct initialization order will help to fix it:

scala> class B {
     |   f(arg)
     | 
     |   lazy val arg = "foo"
     | 
     |   def f(implicit arg: String) = {
     |     println(arg)
     |   }
     | }
defined class B

scala> new B
foo
res3: B = B@3f9ac6e6
like image 99
yǝsʞǝla Avatar answered Jan 25 '23 14:01

yǝsʞǝla