Nullable vs Non Nullable Types


#1

Hey kotlin community,

I think kotlin syntax and the design is overall beautiful. But the distinguish between nullable and non nullable types is unnecessary. Even I doubt that this design is able to protect me from NPEs. I think, introducing these different types, makes the programming more complicated. It has similarities with C++, where on one side you have pointers (nullable) and references (normally non nullable) with different method call semantics. In kotlin, e.g.

  1. foo.bar()
  2. foo?.bar()
  3. foo!!.bar()

When I start a Android Project with kotlin, all the types are nullable types. So that means I use on Activity and Fragments 2) and 3) to call methods.


#2

So you want to propose a language to be working properly only for specific classes in specific framework?


#3

Null pointers are inavoidable. Considering that projects are very rarely without dependencies. What’s about user input in any variation, e.g. web applications etc? What’s about data sources, e.g. csv files, excel sheets, database tables etc.?
I don’t need to mention a framework… it is enough to mention a web service in usage or a developed library important to the domain (developed in java). I see you can isolate every dependency and do it fine. So I don’t see the benefit towards java because it is the same effort/work to do.
On the other hand, it is hard to predict when to use nullable types or non nullable types. I fear the people will use nullable types by default.


#4

You are wrong. Nullables are avoidable, but this is not the point. Kotlin encourages to explicitly state nullability of the value when it comes from java and respects nullability annotation from most commonly used systems (including Android nullness annotations).


#5

Absolutely not true. It is arguable whether null pointers are avoidable on a JVM. But keep in mind that the JVM is just one of the possible compilation targets. Kotlin is a higher level language that provides means to 100% avoid nullpointers. This does not mean that you cannot write Kotlin code that throws a nullpointer. It depends on which platform you target and which platform’s stdlib you use.


#6

I 100% agree with you.
That being said, you can create a NPE in kotlin by leaking this from inside a constructor and the compiler so far has no way of preventing this (even when not targeting JVM). In this case it is up to the programmer to ensure that no NPE happens but it is an edge case in which compiler performance trumps safety IMO.


#7

Yeah, I remember something like that from the Kotlin Puzzlers talk.

But I would classify that as a bug in the compiler. It is not an intended or inherent feature of the language. The nullpointer that we are talking about generally are coming from intended features that provide JVM compatibility.


#8

This is actually a documented: https://kotlinlang.org/docs/reference/null-safety.html

Some data inconsistency with regard to initialization, such as when:
An uninitialized this available in a constructor is passed and used somewhere (“leaking this”);
A superclass constructor calls an open member whose implementation in the derived class uses uninitialized state;

I would not call it a bug. It is an intended weakness of the nullability system, because the other alternative would be to either prohibit many calls from within a constructor or increase compile time by a lot, due to complicated static analysis, which is not worth it because this is an edge case.


#9

For the second cause of this problem, it would be sufficient to prohibit calls to open functions.

It seems to be the same problem that is described by Joshua Bloch in his “Effective Java” book. I forgot the item number, but the problem is described online here:
https://help.semmle.com/wiki/display/JAVA/Non-final+method+invocation+in+constructor

Because of this problem, Joshua Bloch recommends to make all functions that are called from within a constructor either private or final.

Kotlin could easily have enforced this recommendation, which would have eliminated one of the two causes of the nullpointer exception.

For the other case I don’t see a feasible solution. But it is such a specific case, it could be argued that it warrants to have its own exception type, eg UninitializedInstanceAccessException or the already existing UninitializedPropertyAccessException.


#10

Then private final function calls another open function and we’re back at square one.


#11

This strikes me as very strange. Sometimes you need the a concrete representation of “nothing” becuase you don’t have anything. Sometimes you’re not (and cannot be) furnished with a value until later. Sometimes you want to pass a value along that is nothing on purpose.

I declare ‘null’ values for all value types (integers, reals, and custom ones) in addition to reference types becuase I find them so fundamental. It was a good design decision that makes the code conceptually clean and consistent.

Maybe you’re making some other point. If something cannot be null then I check for it, just like other invalid values. It never causes any problems. I find this sort of “coercive” language design annoying, but luckily Kotlin is less aggressive about it than say Swift.


#12

This is not about possibility, but about your personal code style. Even in Java you can avoid nulls by using Optionals. Some languages do not have nulls at all.


#13

OK, so you want “language supported” nulls instead of “naked” nulls. That makes more sense.


#14

Even if nulls were unavoidable, that doesn’t mean that the Kotlin type system is bad. There is empirical evidence of teams switching a codebase to Kotlin and it removes most, if not all, of the NPEs the program generated.
As for people defaulting to nullable types everywhere, this is also completely unsubstantiated. People want to avoid having to deal the with null as much as possible, so they default their types to nonnullable until they find out that the variable needs to deal with it. And even if the language makes it easy to deal with null, people tend to transform the null to some sort of default value quickly so they don’t have it propogate.


#15

As hard as it may sound, IMO, nulls not only should be avoidable, but not possible at all. All though Kotlin thoughtfully provide as? to cast cast-able, in Java you can downcast null to Object and then cast it to literally any other type.

Integer i = null;
Object o = (Object) i;
Runnable r = (Runnable) o;

One shudders to imagine what inhuman thoughts lie behind that code, but it’s still possible and, ironically, will only work with null.

From this point of view I believe Optional is far superior way to represent “maybe” values.
Rust’s way to do that is my favorite: https://doc.rust-lang.org/std/option/enum.Option.html


#16

Optional has different pros and cons to kotlins null system. I would not call it far superior. I would not even call it superior.
IMO kotlins null system does exactly the same thing as Optionals do. It’s a way to tell the type system that a value can be null, which enables the compiler to force you to check the existence of a value before accessing it. Kotlins null system does exactly that. Does that mean that kotlins null system prevents all “normal” variables from being not null? No, but that is not possible on the jvm as long as you leave the kotlin eco system.
The same would be true for optionals. Optionals don’t stop java passing null where it is not supposed to and Optionals don’t stop you from doing stupid stuff using reflection.
Could Kotlin prevent the problem with NPEs in constructor calls. Yes, but that also has nothing to do with Optionals vs the nullable types.

I never have used rust before so I can’t compare the nullable types with rusts optionals. Does rust have null at all or is rust just using Optionals instead of null? In that case, this is one way of solving the NPE problem, but I would not call it the only way.
Also you have to consider that kotlin is limited by the features of the JVM so comparing kotlin to rust is not always fair. When comparing to system languages though (lets say kotlin native vs rust) I think Optional vs the way kotlin does it, is just a question of syntax. Yes in the background they work differently but this does not really matter to the programmer (in most cases. I won’t argue that knowing those details is not important). The effect of both of them is the same: removing NPEs where not intended by the programmer.

For a system language yes, but on the jvm I don’t see how you could prevent all nulls when interacting with java. What stops java from passing null as an argument when a Optional is expected, what stops it when it expects a non null type?
Same goes for your example. I don’t see how it can be used as an argument for Optionals. Well yeah I do. Optional.None (or however you might call it) still contains type information while null doesn’t. But does that really affect any real world problem. There will always be ways how people can write bad and unnecessarily stupid code. Systems like Optionals or kotlins nullable types aren’t supposed to prevent us doing that. They are there to prevent mistakes when we don’t pay attention. Nothing will ever stop any programmer who wants to write stupid and bad code. (Also there is a valid argument to be made for the idea that null should not hold any type info).


#17

Optionals, hypothetically, hold type info, nullables in Kotlin as they are don’t (Even Optionals in Java as far as I know don’t). Thus casting null to Any? and then back to anything else is quite possible.

Casting hypothetical Optional of some type A to Optional of some type B is, ideally, only successful when cast of type A itself to type B is possible.

Rust’s Option is just template union type.

“Option” is the only safe way to represent value that might not be there
(Technically there is ptr::null, but it can only be applied to raw pointers, which are uncommon and only in use when some system-level or ffi stuff is taking place)

As long as Optional doesn’t hold type info – yes

Null and nullable types, in the very case of its usage (when value is null), don’t hold type info.
When some nullable value is actually present – it’s all fine. But null does not have it’s own type nor it knows of which type it should be therefore when nullable value of ambiguous type is not present it theoretically can be casted to anything. So that it is possible to end up with code that works fine as long as nullable value is not present.

And, as far as I know, the problem solved when Optional type holds type info independently of value’s presence.


#18

Optionals are just type-safe, language supported nulls with features that force you to deal with them in certain restricted ways. You cannot avoid nulls, nor the “none” side of optional values or whatever you want to call nulls, even with type safety or any other form of language safety.

Your example illustrates this. The null value has a null type (which includes only the null value), which is a subtype of all types, so it can be assigned to any type, exactly becuase the concept of null is natural (and logically necessary) for all types.


#19

You’re confusing the type of a variable with the type of a value. The type of a variable is a constraint on what types of values that variable can hold. Optionals have a type, as do nullables, it’s just they can contain an extra value besides their “natural” values, namely the “none” or null value.


#20

Yes, but you can implement Optionals in a way that the none value itself knows which type it is supposed to have.

sealed class Optional<T> {
    Some(val value: T)
    None(val type: KClass<T>)
}

That way you can ensure that your casts are valid even for None objects. Whether this is necessary or when this would be important, I don’t know.