Explicit Nulls and Named Tuples in Scala
Modernizing data structures and eliminating the billion-dollar mistake.
Scala continues to push the boundaries of type safety and developer ergonomics. Two of the most impactful features for writing clean, predictable code are Explicit Nulls and Named Tuples. While one hardens your codebase against runtime crashes, the other provides lightweight, expressive data grouping without the boilerplate of case classes.
Explicit Nulls: Taming the Billion-Dollar Mistake
Historically in Scala (and Java), Null was treated as a subtype of all reference types (AnyRef). This meant that a variable of type String could silently hold a null value, leading to the dreaded NullPointerException at runtime.
Scala 3 introduced Explicit Nulls as an opt-in feature (enabled via the -Yexplicit-nulls compiler flag). When enabled, the type hierarchy changes fundamentally: Null is no longer a subtype of AnyRef.
How it works in practice
With explicit nulls enabled, types are non-nullable by default. If you want a variable to potentially hold a null value, you must use a Union Type (T | Null).
// With -Yexplicit-nulls enabled
val safeString: String = "Hello, World!"
// val brokenString: String = null // ERROR: Found Null, expected String
val nullableString: String | Null = null // This is perfectly valid
Working with Nullable Types
When you have a union type String | Null, the compiler forces you to check for null before calling methods on it. This makes nullability a compile-time check rather than a runtime surprise.
val text: String | Null = getTextFromDatabase()
// Cannot do text.length directly!
if (text != null) {
// Flow typing kicks in: text is smart-cast to String inside this block
println(text.length)
}
null, Scala 3 patches the types of Java methods so they return T | Null under this flag, ensuring you handle Java API responses safely.
Named Tuples: Lightweight Records
Scala has always supported tuples (e.g., (String, Int)), but accessing their elements via _1, _2 is historically opaque and unreadable. Case classes solve this by giving names to fields, but they require a separate definition and come with a slightly heavier footprint.
Introduced experimentally and refining over recent Scala 3.x releases, Named Tuples bridge this gap. They give you the structural convenience of a tuple with the readability of a record.
Syntax and Usage
Named tuples allow you to attach labels directly to tuple elements. The syntax is clean and intuitive:
import scala.language.experimental.namedTuples
// Creating a named tuple
val user = (name = "Alice", age = 30, active = true)
// Accessing elements by name instead of _1, _2
println(s"User ${user.name} is ${user.age} years old.")
Type Signatures
The type of a named tuple is structurally defined by its names and the types of its elements. You can use this in function signatures to create highly readable APIs without declaring intermediate case classes.
type Point3D = (x: Double, y: Double, z: Double)
def calculateDistance(p1: Point3D, p2: Point3D): Double = {
Math.sqrt(
Math.pow(p2.x - p1.x, 2) +
Math.pow(p2.y - p1.y, 2) +
Math.pow(p2.z - p1.z, 2)
)
}
val start = (x = 0.0, y = 0.0, z = 0.0)
val end = (x = 10.0, y = 5.5, z = 2.0)
calculateDistance(start, end)
Named tuples are an excellent fit for returning multiple values from a function, providing ad-hoc data groupings, or parsing intermediate data structures before mapping them to formal domain models.