8

I am in the process of learning Kotlin, and reading about the lateinit keyword makes me doubt its usefulness. Consider this code:

var testString: String? = null

lateinit var lateTestString: String

fun print() {
    print(testString?.length)

    print(lateTestString.length)
}

Here the only difference in getting the length of the string is by checking if it is null or not by using the ?. operator. Is using the lateinit a shortcut for not having to add that extra question mark when accessing properties or invoking methods? Just by that fact I think it is more worth having to add that extra question mark than getting an exception when accessing the lateinit one.

More research showed me that lateinit is good for injections and/or unit tests where the variable has not been initialized yet, but it will be. However, is it not worth having that extra ?. instead of just . to not risk an exception?

  • 1
    I just can't get to terms with "having that extra `?.` to _not risk an exception_". Do you not risk printing `null` instead of the meaningful result? Either a) you don't and it's acceptable behavior -- so clearly you're not talking about a use case for `lateinit`, or b) yes, you are risking incorrect behavior which will not be detected by the running program, and that sounds strictly worse than "risking an exception". – Marko Topolnik Jan 10 '18 at 14:34
  • I am trying to get a grasp on the lateinit keyword and everyone here have given me plenty of examples. Thanks! – Simon Zettervall Jan 10 '18 at 14:43

4 Answers4

18

lateinit keyword exists to enable one specific scenario: when your field can't be null, but you also can't initialize it in the constructor or with a constant value. It is on you to make sure that you initialize the value before using it. If you don't, you get a special exception with clear meaning.

The difference between lateinit usage and "normal" nullable field with ?. is that the latter conveys a wrong message about the code: "this thing can sometimes be null". When, in fact, it can't. It is just initialized later than usual (with dependency injection instead of constructor, for example).

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
  • 2
    But the value **can** be null, is that not a problem? Sure from the DI perspective it is going to be set, but is that the only case? Then I think the `lateinit` is unnecessary because it relies on a framework. It should be a var then because it will first be null and then set. Anyway, I do understand what you mean about sending the right message and it makes the code a bit clearer that this is expected to _always_ be non-null. However I see risks with this approach too as Kotlin is coming out as a "null safe language" and it is a waste to not use the wonderful non-null approaches. – Simon Zettervall Jan 10 '18 at 13:29
  • 2
    @SimonZettervall: honestly, I don't see a violation of null safety here. If you forget to init the thing, you will not get a mysterious NPE. Also, Marko nailed the wording: it is __effectively__ non-null, but we have to make it _technically_ nullable, because type system is not powerful enough. – Sergio Tulentsev Jan 10 '18 at 13:31
  • 1
    Yes of course I will get that error, but imagine we have a complex system where different kind of errors can happen. You added some code to support a new part, the tests pass, but then because of an unexpected error the `lateinit` variable was called without having been called beforehand, then it crashes. `lateinit` maybe should only be used with DI-frameworks or unit tests where one is absolutely sure it will be initialized and not put the keyword in the business logic? – Simon Zettervall Jan 10 '18 at 13:36
  • 4
    @SimonZettervall: "then it crashes" - consider the alternative: The system _formally_ keeps running, but it just (silently!) doesn't do half of the things it was supposed to do. And you have no idea about this. Might be months before you discover the bug. Depending on your requirements, this might be a much worse outcome. This is the tradeoff here, basically. – Sergio Tulentsev Jan 10 '18 at 13:39
  • I agree completely about the fail fast approach and that the consequences might be bigger if we let the system run formally. However not using the null safety which is a big feature of the language is a shame. – Simon Zettervall Jan 10 '18 at 13:51
  • 4
    @SimonZettervall: well, normally you enjoy the null safety and don't use lateinit. But when you _have to_ (like that example with android activity), you don't really have a choice. – Sergio Tulentsev Jan 10 '18 at 13:59
5

Is using the lateinit a shortcut for not having to add that extra question mark

Actually it's much closer to a shortcut for !!. I use it a lot in my code, for the reasons I'll try to describe.

Those two !! have been chosen deliberately to attract attention to places in the code where you "take a bet against the type system", so to speak. When used at proper places, that's exactly what you want, but what about all those vars in real-life projects which are effectively non-null, just the type system is too weak to prove it? I'd hate to see the proliferation of !! all around my codebase when I can easily ascertain they are initialized. This noise would weaken the strong signal that !! sends.

When you see a lateinit var in the code, you know you just have to look up whatever initialization method the surrounding context specifies, to convince yourself that everything's fine. It is very easy to check it's used correctly and I've never seen a bug stemming from it.

It is actually very pleasing to see the Kotlin designers putting the concerns of the real-life developer above strict type formalisms.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • Imagine yourself that you are working in a big, complex system. The system is doing some asynchronous work. When the work is done, the `lateinit` variable is set. In the middle of the work, something goes wrong and the work is canceled. The variable is not set. You try to use this variable. Bam crash. Maybe the `lateinit` should not have been there, maybe it was really hard to spot that there was just a slight chance of an error that could occur. Using the `?.` operator removes the need to double verify the flow, at the cost of 1 extra character. – Simon Zettervall Jan 10 '18 at 13:45
  • 2
    @SimonZettervall: in this case, you'd be misusing `lateinit`. Async task being canceled/aborted is not an exceptional situation and if this leaves var uninitialized, then it must clearly be nullable. – Sergio Tulentsev Jan 10 '18 at 13:48
  • 2
    Furthermore, `?.` is _not_ a replacement for `lateinit`, it changes the behavior. If you are looking for ways to ignore the fact that a var is `null`, or to act differently in those cases, then you are not talking about a use case for `lateinit`. You are talking about a scenario where the code actually _can_ observe a `null` as a legitimate value. – Marko Topolnik Jan 10 '18 at 13:53
  • 1
    Do you agree that using `lateinit` emphasizes fail fast? While I think that is a good thing, I wonder why Kotlin is having the null safety as a language feature. I could just go with the old java style and check `if (x != null)`. – Simon Zettervall Jan 10 '18 at 13:55
  • 3
    I don't wonder because I'm a heavy user of this feature and it's one of the best things about Kotlin for me. You are of course welcome to completely ignore it and strictly use nullable types everywhere, along with all the explicit if-checks instead of `!!` and `?`. I would advise, though, not to do this to any code any other Kotlin programmer will have to look at. – Marko Topolnik Jan 10 '18 at 14:00
4

I'm not from the Jetbrains team, so maybe I don't have a clear picture here, but I agree with you in that lateinit looks like not a good construct.

The original idea when lateinit was added was that we have some frameworks (hint: Android) where occasionally the user of the framework does not have the access to the class's constructors (hint: Android's Activity class), to he can not initialize some property in the constructor or init block. But, due to the fact that these classes have some kind of lifecycle, we can be sure that the property foo will be initialized before it's been used for the first time, because, for example, initialization happens in onCreate(), while the property is used in onResume(), which happens later.

(Somewhere in the past, L - lazy programmer, J - Jetbrains):

L: Hey, Jetbrains! We're lazy, we don't want that extra question mark if we're sure the property will be initialized. Could we please mark it somehow to overcome Kotlin's null safety?

J: Yes, sure! Let's slap in lateinit modifier.

Wonderful idea?

No.

Because in later versions of the language its creators decided to add new syntax for the lateinit properties. I could be wrong (didn't pay much attention to it), but it looks like foo::isInitialized. The reason is that this modifier is being misused (or maybe it's flawed from the very beginning?), so we need to attach some additional checks to it.

So, basically, we're trading the question mark along with the whole null safety for the wonderful opportunity to perform foo::isInitialized checks to prevent UninitializedPropertyAccessException (or something).

Community
  • 1
  • 1
aga
  • 27,954
  • 13
  • 86
  • 121
  • I was afraid this was the case. I guess using the `?.` operator and omitting `lateinit` is the way to go then. – Simon Zettervall Jan 10 '18 at 13:33
  • Your answer is based on the false premise that `lateinit` is there to save you from using `?.`. It is actually there to save you from polluting the code with `!!`, and there is no trading of null safety going on. It is for cases where you _cannot_ have null safety guaranteed at compile time. – Marko Topolnik Jan 10 '18 at 15:02
  • Hmm, why should I pollute my code with `!!` if I have safe calls and other similar capabilities? – aga Jan 10 '18 at 15:33
  • 1
    @MarkoTopolnik The way I see it, is that `lateinit` is the thing which (when used improperly) returns us back to where we started: Java way of handling null safety (or non-initialized properties, whatever, I'm talking about general idea). In case of Java it's on the developer's shoulders to make sure he does not touch the field which wasn't initialized (or references the null value). In case of Kotlin we have this nice idea of compiler making sure you mark the field/property as either nullable or not nullable and then act accordingly. – aga Jan 10 '18 at 15:44
  • And lateinit breaks this idea. Yes, you won't have NPE if you touch non-initialized reference, you'll have another exception. Does it really matter which kind of exception you have if you could omit it altogether by following the principle of distinguishing between nullable and non-nullable references? Yes, there're some cases where we're sure that `foo` will be non-nullable, but we can not init it with some value when the object is created. In my opinion, it doesn't mean that we should create some bypass for these special cases, because by creating this bypass we destroy the general idea. – aga Jan 10 '18 at 15:49
  • 3
    Basically, it bothers you that the language provides a feature you personally don't like. I understand, but I'm at the same time thankful that the Kotlin designers are attentive to the wishes of many other users like me and did introduce this life-saver. I have null-safety wherever I can have it, and where I can't, i have the next best thing which is `lateinit`. – Marko Topolnik Jan 10 '18 at 20:34
  • 1
    Well, I can't say for everybody, that's why I started with "The way I see it". I get your point. – aga Jan 11 '18 at 07:04
0

lateint means later initialize
in this code you get error. You have to initialize the lateTestString before calling it.

var testString: String? = null

lateinit var lateTestString: String

fun print() {
    print(testString?.length)

    print(lateTestString.length)
}
  • I understand that none of the variables has been initialized, what I am asking is maybe it is an unnecessary keyword if the developers only have to add an `?.` operator. – Simon Zettervall Jan 10 '18 at 13:13
  • yes it is not necessary if user declare at first time. – Yogesh Choudhary Paliyal Jan 10 '18 at 13:16
  • what about case where your value cannot be of type `type?` and is initialized by injection for example ? – Benjamin Jan 10 '18 at 13:17
  • I wonder if the `lateinit` keyword is redundant/unnecessary and creates risks with exceptions when the user can only just add a `?.` operator. What you are telling me now is that `lateinit` is not necessary when assigning the variables and I totally understand that, it is not what I am asking about. – Simon Zettervall Jan 10 '18 at 13:19