Cyclic reference involving ...
Cyclic reference involving ...
DodaTech
3 min read
The “Cyclic reference” error in Scala occurs when two objects or vals depend on each other during initialisation, preventing safe evaluation order.
What It Means
Scala initialises objects and vals eagerly by default. When object A references object B and object B references object A at initialisation time, the compiler detects the cycle and reports “Cyclic reference involving …”. This can also happen with val initialisation loops within the same class or across companion objects, where the compiler cannot determine a valid evaluation order.
Why It Happens
- Two singleton objects reference each other at construction time.
- A class and its companion object have circular
valdependencies. - A sealed trait hierarchy has a circular type reference between its subtypes.
- Lazy evaluation is needed but
val(eager) was used instead oflazy val. - Two case classes contain fields of each other’s types without using
Optionor indirection. - A
valinitialiser references itself (e.g.,val x: Int = x + 1).
How to Fix It
1. Use lazy val to break the cycle
// ❌ Cyclic reference involving A and B
object A {
val bValue: Int = B.value
}
object B {
val value: Int = A.bValue + 1
}
// ✅ Use lazy val to defer initialisation
object A {
lazy val bValue: Int = B.value // computed on first access
}
object B {
lazy val value: Int = A.bValue + 1
}2. Restructure the dependency direction
// ❌ Circular dependency
class User(name: String) {
val profile: Profile = new Profile(this)
}
class Profile(user: User) {
val displayName: String = user.name
}
// ✅ Break the cycle with a shared data object
case class UserData(name: String, email: String)
class User(data: UserData) {
val profile: Profile = new Profile(data)
}
class Profile(data: UserData) {
val displayName: String = data.name
}3. Use sealed trait based design for type cycles
// ❌ Cyclic reference in type aliases
trait A { type B }
// Sometimes cycles can appear with complex type members
// ✅ Restructure to avoid mutual type references
trait Base
trait A extends Base
trait B extends Base4. Use Option for self-referencing structures
// ❌ Cyclic reference (case class cannot contain itself directly)
case class Node(name: String, parent: Node)
// ✅ Use Option to break the cycle
case class Node(name: String, parent: Option[Node])
val root = Node("root", None)
val child = Node("child", Some(root))5. Extract shared state into a separate object
// ❌ Two objects with mutual references
object Config {
val timeout: Int = Service.defaultTimeout
}
object Service {
val defaultTimeout: Int = Config.timeout
}
// ✅ Extract the shared constant
object Defaults {
val defaultTimeout: Int = 5000
}
object Config {
val timeout: Int = Defaults.defaultTimeout
}
object Service {
val defaultTimeout: Int = Defaults.defaultTimeout
}Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro