Nullable Generics not compatible between classes


#1

Hi everybody.

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.


#2

Even though I tell kotlin that T is a nullable type via type constraint it doesn’t accept nulls.

You don’t say that. What you’re telling the compiler is that the placeholder may be of a nullable type.

 val t = NullableIssue<String>()

will give you the field

private var _value : String = null

Which isn’t possible.

made by sveta isakova


#3

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

#4

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


#5

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?.


#6

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.


#7

Like @Jonathan.Haas and I said, use ? at the places where you want to allow null.

a good hint:
Just write the class like you wouldn’t allow null-values.
That’s often, just as in this case, the best way.

If you want I can give the answer, but I think puzzling for yourself is the best way to understand it.
But I have the answer ready, if you ask for it.


#8

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?>.


#9

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
        }
    }
}

#10

This problem is because of the type of the list.
It’s to prevent the following code:

var notNull = listOf<String>()
notNull = listOf<String?>(null)
val t : String = notNull[0]

#11

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.

Thanks for your help.


#12

the shorter variant:

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)
    }
}