Eric Cochran
Eric Cochran

Reputation: 8574

Constrain Kotlin generic to not null in one case

I have a generic type (T: Any?) that I need constrained in one situation never to be null:

class Repository<T> { // T may be a non-null or nullable type.
  fun getFromFoo(): T {}
  fun getFromBar(): T {} // This is never null. Can I mark it as so?
}


val repository = Repository<String>()
val fromFoo: String = repository.getFromFoo() // Returns a non-null String.
val fromBar: String = repository.getFromBar() // How do I make this work?


val repository = Repository<String?>()
val fromFoo: String? = repository.getFromFoo() // Returns a nullable String.
val fromBar: String = repository.getFromBar() // How do I make this return a non-null String?

While I would ideally refactor these into separate types (like FooRepository and BarRepository), is there any way to get this type constraint functionality?

Upvotes: 8

Views: 1889

Answers (6)

mako
mako

Reputation: 181

You can now do this easily in Kotlin 1.7 with definitely non-nullable types:

class Repository<T> { // T may be a non-null or nullable type.
    fun getFromFoo(): T { TODO() }
    fun getFromBar(): T & Any { TODO() } // This is never null.
}

Upvotes: 4

Ilya
Ilya

Reputation: 23164

What you need is the type T & Any — an intersection of generic T and Any. It represents such subtype of T that can never contain nulls.

Unfortunately the intersection types and this intersection in particular currently are non-denotable in Kotlin, meaning that you can't declare a return type of a function to be non-nullable subtype of T.

Upvotes: 7

hudsonb
hudsonb

Reputation: 2294

class Repository<T : Any> { // T may only be a non-null type.
  fun getFromFoo(): T? {} // This may return null.
  fun getFromBar(): T {} // This is never null.
}

val repository = Repository<String>()
val fromFoo: String? = repository.getFromFoo()
val fromBar: String = repository.getFromBar()

Require a non-null type. You can selectively indicate nullability via T? vs T for your methods/properties.

EDIT: Revised solution based on question clarification found in the comments

My impression now is that you want one method to always return a non-null value, even if T is of a nullable type. The short answer is no, there is no way to do this.

You could however, define two generics:

 class Repository<T, N : T> {
  fun getFromFoo(): T {}
  fun getFromBar(): N {}
}

val repository = Repository<String?, String>()
val fromFoo: String? = repository.getFromFoo()
val fromBar: String = repository.getFromBar()

<T, N : T> is one option, but it doesn't enforce that N be of a non-nullable type. <T, N : Any> is another option, this forces N to be a non-nullable type, but it's not longer forced to extend T. Pick your poison.

This makes constructing Repository uglier but gives you the convience of it being non-null at all of the call sites.

Upvotes: 4

s1m0nw1
s1m0nw1

Reputation: 82087

You should not parametrize with nullable types. You should rather use non-null types and then make the nullability explicit where really needed:

class Repository<out T : Any> {
    fun getFromFoo(): T? {
        TODO()
    }

    fun getFromBar(): T {
        TODO()
    }
}

You say that T is supposed to be a non-null type (T: Any), and say that getFromFoo() might return T?, which works totally fine.

Upvotes: 1

Willi Mentzel
Willi Mentzel

Reputation: 29884

Refactoring into two separate classes would be a better idea, but since you don't want that you could use two generic types:

class Repository<T, S: Any> {
    fun getFromFoo(): T? { /* ... */ }
    fun getFromBar(): S { /* ... */ }
}

Upvotes: 0

kabuko
kabuko

Reputation: 36302

Do the opposite. Assume your generic type is not nullable:

class Repository<T> {
  fun getFromFoo(): T? { ... }
  fun getFromBar(): T { ... }
}

val repository = Repository<String>()
val fromFoo: String? = repository.getFromFoo()
val fromBar: String = repository.getFromBar()

Upvotes: 2

Related Questions