Is adding a trait method with implementation breaking backward compatibility?

1.7k views Asked by At

I am confused regarding backward compatibility when adding a method with default implementation to a trait. Like:

Previous Version

trait Foo

New Version

trait Foo {
  def verifyConsistency: Option[String] = ??? // provide default implementation
}

The Migration Manager reports this addition as a binary incompatibility. Is that correct?

1

There are 1 answers

1
Régis Jean-Gilles On BEST ANSWER

Well yes it is correct.

When you define trait Foo, it will under the hood create both a (JVM) interface Foo and a (JVM) class Foo$class with all the method implementations defined as static methods. The corresponding java code would look like something like this (for your new defintion of Foo):

interface Foo {
  Option<String> verifyConsistency();
}

class Foo$class {
  static Option<String> verifyConsistency(Foo self) {
    Predef.???();
  }
}

When you mix Foo into a concrete class Bar, what happens at the JVM level is that Bar extends the interface Foo, and it implements method verifyConsistency by simply forwarding the call to Foo$class:

class Bar implements Foo {
  Option<String> verifyConsistency() {
    return Foo$class.verifyConsistency(this); // simple forwarding
  }
}

The reason why it is done this way is that the JVM object model does not support multiple inheritance. The traits implementations cannot be simply put in classes that you would extend from, because you can only ever extend a single class on the JVM.

The take away of this situation is that everytime a concrete class mixes a trait, the class defines "stub" methods for each member of the trait (those methods simply forward to the actual implementation, which is a static method).

One consequence is that if you add a new method to a trait, even if you define an implementation it is not enough: concrete classes that mix the trait need to be recompiled (so that a stub for the new method is added to the class). If you don't recompile those classes, your program will fail to run, as you would now have a class that is supposedly concrete (non abstract) AND extend the corresponding interface but actually miss the implementation for the new method.

In your case this means having concrete classes that extend interface Foo but do not have any implementation for verifyConsistency.

Hence the binary incompatibility.