I’m currently facing a small problem with generics and their nullability. I have a custom data structure that should be able to hold nulls but somehow I cannot make Kotlin satisfied about the null usage.
First a simplified example:
public class NullableIssue<T : Any?> {
private var _value:T = null // null cannot be a value of non-null type T
}
Even though I tell kotlin that T is a nullable type via type constraint it doesn’t accept nulls.
My concrete usecase is a linked-list alike data structure which returns null values if it is empty. Here several examples which all fail because of some nullable type errors. Can maybe somebody point me out how I can satisfy Kotlin for my example? It seems to me that 2 classes cannot be compatible if there is a nullable generic in the game.
Attempt 1: use nullable type constraint
public class LinkedNode<T : Any?> {
public var list : Linked<T>? = null
public var value : T = null // error: null cannot be a value of non-null type T
}
public class Linked<T : Any?> {
public var firstNode: LinkedNode<T>? = null
public fun removeFirst(): T {
var firstNode = this.firstNode
if (firstNode == null) return null // error: null cannot be a value of non-null type T
return firstNode.value
}
public fun addFirst(value: T) {
if (this.firstNode == null) {
var newNode: LinkedNode<T> = LinkedNode<T>()
newNode.value = value
newNode.list = this
this.firstNode = newNode
}
}
}
Attempt 2: make type parameter nullable everywhere
public class LinkedNode<T> {
public var list : Linked<T?>? = null
public var value : T? = null
}
public class Linked<T> {
public var firstNode: LinkedNode<T?>? = null
public fun removeFirst(): T? {
var firstNode = this.firstNode
if (firstNode == null) return null
return firstNode.value
}
public fun addFirst(value: T?) {
if (this.firstNode == null) {
var newNode: LinkedNode<T?> = LinkedNode<T?>()
newNode.value = value
newNode.list = this // error: type mismatch, Required Linked<T?>? Found Linked<T>
this.firstNode = newNode
}
}
}
I also tried to combine nullable types and type constraints but nothing makes Kotlin happy. Only explicitely casting this to Linked<T?> seems to work, but this leads to a unchecked cast warning.
Please note that the code posted here tries to explain the problem I am facing with the compiler on nullable generics. I’m not looking for better solutions how to make linked lists or eliminate nullability (e.g. by introducing exceptions). My actual code is a bit more complex, is partly auto-generated from another language which makes things like adding a cast in those scenarios not as straight forward.
But String should not be compatible with Any?, hence violating the type constraint. Only NullableIssue<String?> should be allowed which would allow the usage of null. Or does Any? mean something different than any nullable type? I would have expected it restricts the usage to only types where nullable is specified:
let a = NullableIssue<String>(); // error: String? is not compatible with Any?
let b = NullableIssue<String?>(); // ok
let c = NullableIssue<Int?>(); // ok
as you see, String is a subtype of String? is a subtype of Any?
Therefor, if you want to return null at some places, just add a question-mark.
When you want to have the type itself, use the generic without question-mark
No, you tell Kotlin that T is a subtype of Any?. That doesn’t mean that T must be nullable. For example non-nullable Int is a subtype of Any? (as you can obviously assign an Int to a variable of type Any?).
Whenever you want to force a nullable type, you have to write T?.
Hmm, from a polymorphism perspective you are right. A not-null string obviously is compatible with a variable that accepts nullable types. Somehow my thinking was inverted on the type constraint perspective. It’s a bit unfortunate that you cannot restrict type parameters to be nullable only (e.g. with an other syntax).
This explains why the simplified example and attempt 1 fail.
Unfortunately my actual problem remains. I would like to tell Kotlin somehow that my type parameters are nullable to make the 2 classes compatible and use nulls where required.
Please look closely at my example in attempt 2. It uses nullable types everywhere, where I want to hold and use null. Everywhere but on the class itself where I’m not aware of any syntax construct where I can hint nullability.
newNode.list = this // error: type mismatch, Required Linked<T?>? Found Linked<T>
newNode.list is of type Linked<T?>? as I declared nullability explicitely. But for this I do not see a way to achive a Linked<T?>.
Attempt 2 fails because you used T? too much. Add the question mark only where you want to force nullable values, not everywhere. If your user has created a Linked<Int> then you don’t want the inner references to be typed as Linked<Int?>. This should work:
public class LinkedNode<T> {
public var list : Linked<T>? = null
public var value : T? = null
}
public class Linked<T> {
public var firstNode: LinkedNode<T>? = null
public fun removeFirst(): T? {
var firstNode = this.firstNode
if (firstNode == null) return null
return firstNode.value
}
public fun addFirst(value: T) {
if (this.firstNode == null) {
var newNode: LinkedNode<T> = LinkedNode<T>()
newNode.value = value
newNode.list = this
this.firstNode = newNode
}
}
}
Thanks for the hint on how a working solution could look like. From a pure Kotlin perspective this code makes sense. I just fear that in my code generation I do not have that fine control about where to use nullable types, but that’s rather the problem of my generator and not Kotlin.
It would have been nice for this use case to define the type parameter as nullable on a global level. I fear I will need to rewrite some parts of my code or rework my generator to solve this issue.
class LinkedNode<T>(
var list : Linked<T>? = null,
var value : T? = null
)
class Linked<T> {
var firstNode: LinkedNode<T>? = null
fun removeFirst(): T? = firstNode?.value
fun addFirst(value: T) {
firstNode = firstNode ?: LinkedNode(this, value)
}
}