1

We know the lambda body is lazily well, because if we don't call the lambda the code in the lambda body is never be called.

We also know in any function language that a variable can be used in a function/lambda even if it is not initialized, such as javascript, ruby, groovy and .etc, for example, the groovy code below can works fine:

def foo

def lambda = { foo }

foo = "bar"

println(lambda())
//      ^--- return "bar"

We also know we can access an uninitialized variable if the catch-block has initialized the variable when an Exception is raised in try-block in Java, for example:

//  v--- m is not initialized yet
int m;

try{ throw new RuntimeException(); } catch(Exception ex){ m = 2;}

System.out.println(m);// println 2

If the lambda is lazily, why does Kotlin can't use an uninitialized variable in lambda? I know Kotlin is a null-safety language, so the compiler will analyzing the code from top to bottom include the lambda body to make sure the variable is initialized. so the lambda body is not "lazily" at compile-time. for example:

var a:Int
val lambda = { a }// lambda is never be invoked
//             ^--- a compile error thrown: variable is not initialized yet
a = 2

Q: But why the code below also can't be working? I don't understand it, since the variable is effectively-final in Java, if you want to change the variable value you must using an ObjectRef instead, and this test contradicts my previous conclusions:"lambda body is not lazily at compile-time" .for example:

var a:Int
run{ a = 2 }// a is initialized & inlined to callsite function

//      v--- a compile error thrown: variable is not initialized yet
println(a)

So I only can think is that the compiler can't sure the element field in ObjectRef is whether initialized or not, but @hotkey has denied my thoughts. Why?

Q: why does Kotlin inline functions can't works fine even if I initializing the variable in catch-block like as in java? for example:

var a: Int

try {
    run { a = 2 }
} catch(ex: Throwable) {
    a = 3
}

//      v--- Error: `a` is not initialized
println(a)

But, @hotkey has already mentioned that you should using try-catch expression in Kotlin to initializing a variable in his answer, for example:

var a: Int = try {
    run { 2 }
} catch(ex: Throwable) {
    3
}

//      v--- println 2
println(a);

Q: If the actual thing is that, why I don't call the run directly? for example:

val a = run{2};

println(a);//println 2

However the code above can works fine in java, for example:

int a;
try {
    a = 2;
} catch (Throwable ex) {
    a = 3;
}

System.out.println(a); // println 2
holi-java
  • 29,655
  • 7
  • 72
  • 83

2 Answers2

2

Q: But why the code below also can't be working?

Because code can change. At the point where the lambda is defined the variable is not initialized so if the code is changed and the lambda is invoked directly afterwards it would be invalid. The kotlin compiler wants to make sure there is absolutely no way the uninitialized variable can be accessed before it is initialized, even by proxy.

Q: why does Kotlin inline functions can't works fine even if I initializing the variable in catch-block like as in java?

Because run is not special and the compiler can't know when the body is executed. If you consider the possibility of run not being executed then the compiler cannot guarentee that the variable will be initialized.

In the changed example it uses the try-catch expression to essentially execute a = run { 2 }, which is different from run { a = 2 } because a result is guaranteed by the return type.

Q: If the actual thing is that, why I doesn't call the run directly?

That is essentially what happens. Regarding the final Java code the fact is that Java does not follow the exact same rules of Kotlin and the same happens in reverse. Just because something is possible in Java does not mean it will be valid Kotlin.

Kiskae
  • 24,655
  • 2
  • 77
  • 74
  • thanks for your answer, sir. your answer is same with me, but why I wrong? – holi-java Jul 21 '17 at 18:07
  • It would seem the compiler would know that 'a' is unused until the lambda is called. A better place for the error would be where the lambda is called (or passed to some function). I understand closure might be an issue. – Les Jul 21 '17 at 19:32
  • @Les Hi. in fact, the compiler know the variable `a` whether is initialized because the compiler must make sure everything is initialized to supports null-safety in Kotlin. it must analyzing the code at the compile time from top to bottom. but why the code `run{a=2}` , I can't understand it. Indeed, it should be works fine without any `catch-block` in my second question. – holi-java Jul 21 '17 at 20:04
  • @kiskae - the `run` function is not special, it will run the block and return, after which `a` will have been initialized. It's an inline function, otherwise I would agree that it is too much for the compiler to have to figure out whether or not the function actually called the lambda or not. – Les Jul 21 '17 at 21:31
1

You could make the variable lazy with the following...

val a: Int by lazy { 3 }

Obviously, you could use a function in place of the 3. But this allows the compiler to continue and guarantees that a is initialized before use.

Edit

Though the question seems to be "why can't it be done". I am in the same mind frame, that I don't see why not (within reason). I think the compiler has enough information to figure out that a lambda declaration is not a reference to any of the closure variables. So, I think it could show a different error when the lambda is used and the variables it references have not been initialized.

That said, here is what I would do if the compiler writers were to disagree with my assessment (or take too long to get around to the feature).

The following example shows a way to do a lazy local variable initialization (for version 1.1 and later)

import kotlin.reflect.*

//...
var a:Int by object {
    private var backing : Int? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int =
        backing ?: throw Exception("variable has not been initialized") 
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        backing = value
    }
}
var lambda = { a }

// ...
a = 3
println("a = ${lambda()}")

I used an anonymous object to show the guts of what's going on (and because lazy caused a compiler error). The object could be turned into function like lazy.

Now we are potentially back to a runtime exception if the programmer forgets to initialize the variable before it is referenced. But Kotlin did try at least to help us avoid that.

Les
  • 10,335
  • 4
  • 40
  • 60
  • :), Hi, it is not I want. you can see here that I talk with @hotkey as further, but my bad english will confused you. If you can solve my confusing, I'll giving additional bounties. https://stackoverflow.com/questions/45238746/why-can-a-use-block-not-safely-initialize-a-var/45238793 – holi-java Jul 21 '17 at 20:13
  • @holi-java, I understand. But I add the answer because people like me with questions about lazy initialization will likely come to this page. – Les Jul 21 '17 at 20:35
  • I'm sorry I confused you. but I'll thank you somewhere. – holi-java Jul 21 '17 at 20:39
  • yeah, the example above using `lazy`, but compiler using `*Ref`. however your code can working, the byte code generated using `*Ref` can't. this is **why** I confused. good catch, but still didn't answer my question since our question are the same, :) – holi-java Jul 21 '17 at 21:03