- Type: Design proposal
- Author: Denis Zharkov
- Contributors: Andrey Breslav, Roman Elizarov
- Status: Submitted
- Prototype: Implemented in 1.3-RC
- Discussion: KEEP-158
- Support top-level
fun main()
as an entry point to a program - Support top-level
suspend fun main(args: Array<String>)
as an entry point to a programargs
parameter can be omitted
- Related platforms: JVM, JS
We'd like to minimize the amount of boilerplate needed to write a small program in Kotlin. Especially, it's important when the program is the first one written in the language, e.g. "Hello, world" or other small samples made for educational purposes.
And mostly, such programs don't need the args
parameter.
- Again, it might be useful when one's learning the language and starting experimenting with coroutines
- Also, one might need it when writing a small utility program using some kind of async API, e.g. ktor-client
- A public top-level function named
main
is considered as an entry point if there's no other entry point with an array parameter (e.g.suspend fun main(args: Array<String>)
,suspend fun main(vararg array: String)
, etc.) in the containing file - Only functions with
Unit
as a return type are considered suspend
modifier is also allowed in the case- As for other entry points, do not report conflicting overloads for parameterless main-functions in the same package if they belong to different files
- Unlike main-functions with parameters, cases like the following are unsupported:
@JvmName("main")
fun noMain() {} // is not an entry point
- Once a parameterless main is used as an entry point, received program arguments are just ignored
Since being compiled straightforwardly a parameterless main function doesn't satisfy the requirements for entry points
on JVM, another synthetic public static method with a String[]
needs to be generated by the compiler.
So, the following code:
fun main() {
println("Hello, world!")
}
should be compiled in something like:
class FileNameKt {
public static void main() {
System.out.println("Hello, world!");
}
/* ACC_SYNTHETIC */
public static void main(String[] args){
main();
}
}
Presence of ACC_SYNTHETIC
is needed for several reasons:
- This declaration is not assumed to be called from Java
- Debugger may use this as an indicator to skip the relevant frame
In Kotlin/JS the invocation of the main
function is controlled by the compiler itself.
Normally it is just another JavaScript statement main([]);
at the end of the module definition.
In case of a main function with no parameters no arguments are passed, i.e. there is a main();
statement at the end of the module definition in the generated JavaScript file.
- A public top-level function named
main
withsuspend
modifier is considered as an entry point if it would be an entry point without the modifier. Examples:
suspend fun main(args: Array<String>) // OK
suspend fun main(args: Array<out String>) // OK
suspend fun main(vararg args: String) // OK
suspend fun main() // OK, by the rules from the previous section
@JvmName("main")
suspend fun noMain(args: Array<String>) // not an entry point: wrong name
object A {
suspend fun main(args: Array<String>) // not an entry point: non-top-level
}
- Semantically, the behavior of such entry point may be slightly different on different platforms.
Briefly, it should work like the body of the entry point is passed as a lambda to
runBlocking
from kotlinx.coroutines. Mainly, the idea is the following:- If no suspension happens, the execution should be the same as for regular non-suspend main function
- Otherwise, the beginning of the function body should be executed in the regular main thread until the first suspension point. Then the program should be run until the started coroutine is not completed
- If the coroutine was completed normally then the whole program should be terminated normally, as well
- If the coroutine was completed with an exception then the whole program should be terminated just like the exception was thrown synchronously and wasn't caught anywhere including the entry point
Because suspend functions have an additional Continuation
parameter they can't be a valid suspension point on JVM, too.
Just like in case of parameterless main, the compiler generates a synthetic bridge having a valid entry-point signature
and delegating to the implementation.
The content of a generated bridge is similar to the following
/* ACC_SYNTHETIC */
fun main(args: Array<String>) {
kotlin.coroutines.jvm.internal.runSuspend {
main(args) // calling the suspend version
}
}
and runSuspend
is defined like:
@SinceKotlin("1.3")
internal fun runSuspend(block: suspend () -> Unit) {
val run = RunSuspend()
block.startCoroutine(run)
run.await()
}
private class RunSuspend : Continuation<Unit> {
override val context: CoroutineContext
get() = EmptyCoroutineContext
var result: Result<Unit>? = null
override fun resumeWith(result: Result<Unit>) = synchronized(this) {
this.result = result
(this as Object).notifyAll()
}
fun await() = synchronized(this) {
while (true) {
when (val result = this.result) {
null -> (this as Object).wait()
else -> {
result.getOrThrow() // throw up failure
return
}
}
}
}
}
Similar to the previous case, Kotlin/JS generates a direct invocation to the main function in the generated JavaScript file.
In case of suspend fun main(args: Array<String>)
if looks like this:
main([], kotlin.coroutines.js.internal.EmptyContinuation);
where EmptyContinuation
is defined as:
@PublishedApi
internal val EmptyContinuation = Continuation<Any?>(EmptyCoroutineContext) { result ->
result.getOrThrow()
}
Basically it launches the suspend main
function with an empty context, and rethrows
any exceptions that have occured during it's execution.
In case of a suspend main
with no parameters, only the continuation is passed:
main(kotlin.coroutines.js.internal.EmptyContinuation);
Both entry points convention enhancements are only applied to top-level functions, while on JVM one can declare an entry point within an object, e.g.:
object A {
@JvmStatic
fun main(args: Array<String>) {
}
}
This proposal does not extend the convention for objects for several reasons:
- Such functions are not Kotlin-way to define an entry point. At least, because they are clearly platform-specific and mostly they remain after Java-to-Kotlin conversion.
- For sake of simplicity
- There's no actual support for such entry points in the language: these functions themselves satisfy signature requirements on JVM without any special treatment by the compiler
- New enhanced entry points convention is not supported by Kotlin Native because it's unclear where a body of a suspend main functions should be executed on different targets (iOS, linux, windows, etc.)