Skip to content

Latest commit

 

History

History
211 lines (172 loc) · 7.77 KB

enhancing-main-convention.md

File metadata and controls

211 lines (172 loc) · 7.77 KB

Enhancing main entry point convention

  • Type: Design proposal
  • Author: Denis Zharkov
  • Contributors: Andrey Breslav, Roman Elizarov
  • Status: Submitted
  • Prototype: Implemented in 1.3-RC
  • Discussion: KEEP-158

Summary

  • 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 program
    • args parameter can be omitted
  • Related platforms: JVM, JS

Motivation

Making args an optional parameter

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.

Allowing suspend modifier

  • 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

Parameterless main

Rules and semantics

  • 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

Implementation details on JVM

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

Implementation details on JS

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.

Suspend main convention

Rules and semantics

  • A public top-level function named main with suspend 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

Implementation details on JVM

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
                }
            }
        }
    }
}

Implementation details on JS

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);

Entry points in singleton instances

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

Remaining questions/issues

  • 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.)