1. Introduction
Scala’s package system keeps the code organized, but referencing packages can get tricky with relative imports. In this tutorial, we’ll explore a special syntax in the import clause using the _root_ keyword.
2. What Is _root_ Import?
In Scala, organizing code with packages is a fundamental practice. However, referencing these packages can get tricky due to relative imports, especially in large projects. This is where the _root_ identifier becomes handy to avoid confusion and ensure the correct type is imported.
We can use a relative or absolute path when importing packages in Scala. Let’s explore this with an example:
package com.baeldung.scala.rootimport.scala
class List(val num: Int) {
override def toString: String = s"MyList[${num}]"
}
object List {
def apply(num: Int) = new List(num)
}
In the above code, we created a custom class List within the package com.baeldung.scala.rootimport.scala. Now, let’s write a simple test to use this List implementation:
import scala.*
val myList: scala.List = List(100)
assert(myList.toString == "MyList[100]")
This test class is in the com.baeldung.scala.rootimport package, one layer above where our custom List is defined. *When we use the statement import scala.*, the Scala compiler first looks at the packages relative to the current position*. As a result, it imports our custom List implementation and creates an instance of that, even though Scala Collection List is available using implicit import through Predef.
But what if we want to use the standard Scala collection List instead? To avoid using the relative path for the import, we can use the _root_ keyword:
import _root_.scala.*
val scalaList: List[Int] = List(100)
assert(scalaList.toString == "List(100)")
Here, instead of using import scala.*, we used import _root_.scala.*. This forces the compiler to use the absolute path of the package regardless of the current package, ensuring it imports the standard Scala collection List.
If we only use import scala.* here without the _root_ prefix, the compiler will utilize our custom implementation of List as it matches the relative path. We can verify that with a test:
import scala.*
val scalaList = List(100)
assert(scalaList.toString == "List(100)")
When we run this test, it fails with the error:org.scalatest.exceptions.TestFailedException: "[MyList[100]]" did not equal "[List(100)]"
This happens because our custom List is being used, while the assert statement expects the List from the Scala collection.
3. Advantages of Using _root_
The advantages of using the _root_ import are:
- Disambiguation: It helps resolve conflicts when different packages of the same name exist in the project, especially from multiple third-party dependencies.
- Clarity: Using _root_ makes it clear that the import is from the absolute root, not relative to the current project
- Maintenance: Make it easy to maintain large projects by reducing the risk of incorrect imports
- Avoiding conflicts with the standard library: Ensures that custom code doesn’t conflict with standard library classes and objects
While _root_ offers precision and clarity, overuse of it can hinder readability.
4. Conclusion
In this brief tutorial, we examined using the _root_ identifier in Scala import statements. It is a powerful tool to manage package namespaces effectively, especially in large projects where package names might overlap. By using _root_, we can ensure precise and unambiguous imports, making the code robust and maintainable and preventing conflicts with the standard Scala library.
As always, the sample code used in this tutorial is available over on GitHub.