in a Kotlin library project, I’m initializing a global / top-level variable like
val foo = bar()
If foo is not used anywhere else in the library, then bar() is not called. However, I need bar() to be called in any case. Is there a way (e.g. via some annotation) to force bar() to be called even if foo is unused? Or is there a better pattern to “automatically” run some code when a library is used?
Not sure what you mean by “your initialization code”, but if I just white foo at the top-level on a single line I’m getting an error saying “Expecting a top level declaration”.
Wooops, I missed the library part. I don’t think there is any way of enforcing initialization code to run with an annotation. I think the best you can do is provide an initMyLibrary function that every consumer has to call at least once.
In the JVM, static fields in a class are initialized when the class is first used. And Kotlin properties at the top level are stored in static fields.
So, once anything in that file is used (like a function, or something else), then the field will be initialized. If there’s a function that must be called in order to use your library, then putting it in the same file as that property will guarantee it’s initialized (and it’ll be initialized before the function’s called)
No. This only works if top level functions or properties depend on it. If a class in the same file depends on it this approach doesn’t work because the class fill be compiled into a separate .class file from the top level properties.
I’ve run into this problem a few times myself, and I haven’t found any pretty or kotlinish solution, yet.
You can either enforce the call of the bar() in the contract of your library, but it is only a written contract, there is no compile time validation on using it.
Also you may check the variable in all your public functions, but that would pollute your code badly and it is error prone (if you left out your check in any of your functions).
So this is a feature one would really desire and it’s easy to show use cases for it. If a vote for library initializer would turn up, I vote for it. It would make life easier.
However, this mechanism is oppose how the java class loading works. The classloader would have to scan the whole classpath looking for init blocks and that’s resource consuming. That’s why it is not done and I think it won’t be done in the close future. That’s why all frameworks (like Spring) needs an entry point to be called implicitly.
When I have to do this kind of initialization, I use reflections library (GitHub - ronmamo/reflections: Java runtime metadata analysis, at last it works on Java8+) and use annotations or collect all implementations of an interface in the classpath. But even then I need an entry point to start the scan. This is not perfect, one could miss the call of the entry point, but it works and is the only way at the moment.
val foo = bar().also { println("foo initialized") }
fun bar(): Int = 6.also { println("bar() called") }
fun someOtherFun() {
println("someOtherFun() called")
}
Then running this code from another file:
fun main() {
println("main")
someOtherFun()
}
Calling someOtherFun() will cause foo to be initialized, and bar() to be called. This will be printed:
main
bar() called
foo initialized
someOtherFun() called
Even though foo is never used, bar() is called. I’ve used this before when there’s some function in the library that the library consumer is guaranteed to call
class FooBar(){ ... }
val foo = bar().also { println("foo initialized") }
fun bar(): Int = 6.also { println("bar() called") }
// different file:
fun main() {
FooBar()
}
will not initialize foo or call bar, because FooBar will be compiled into a separate class-file.
package com.example.android.database
var qqq = 45.also { println("QQQ") }
fun zzz() {}
class Ttt {
val rrrr = 4.also { println("ttt") }
}
with invocation in main activity:
zzz()
println("after zzz")
qqq
qqq
Ttt()
Ttt()
gives:
QQQ
after zzz
ttt
ttt
Notice only once printing QQQ.
code:
Ttt()
Prints
ttt
Invoking only zzz() also prints QQQ that means reading package properties.
Importing doesn’t invoke initialization (may be it gets stripped off on compile). Neither merely creating object Ttt() invoke initialization of package variables. (seems like class and vars are in different files)
IMHO: by policy any library shouldn’t execute any code by them own without asking it explicitly. For example, malicious library can leak your project data somewhere in that way. But, if you insist,
you should invoke that bar() function in all classes init blocks where you need it. That’s how it works if you don’t have a singe start point class.
One thing to note is, that this is (as far as I can tell) not documented anywhere. This is an emergent behaviour based on the way the JVM works and how kotlin compiles top level properties. I don’t think this will ever change for kotlin JVM since this would most liekly require a breaking change to the JVM (which is super unlikely) but this might behave quite differently for Kotlin JS and Kotlin Native.
In any case, a library that needs some initialization code to be run shouldn’t rely on top level properties but instead provide an init function.
That sounds like I’m super untrustworthy
Nah it’s great to have independent verification, especially for something so wiered