Kotlin’s intelligent inference is a hallmark of his language.

Intelligent inference, able to automatically convert type based on type detection.

However, intelligent inference is not as powerful as it should be. For example, the following code fails to infer, resulting in compilation failure:

funString? .isNotNull(a):Boolean {
    return this! =null && this.isNotEmpty()
}

fun printLength(s:String? =null) {
    if(! s.isNotNull()) { println(s.length)// Only safe (? .). or non-null asserted (!! .). calls are allowed on a nullable receiver of type String?}}Copy the code

Because the compiler will infer s as value-parameter s: String? =… It’s not a String. Intelligent inference fails, and the code does not compile.

Make the following changes to the above code to compile successfully:

fun printLength(s:String? =null) {
    if(! s.isNullOrEmpty()) { println(s.length) } }Copy the code

IsNullOrEmpty () is a Kotlin library extension to String.

@kotlin.internal.InlineOnly
public inline funCharSequence? .isNullOrEmpty(a): Boolean {
    contract {
        returns(false) implies (this@isNullOrEmpty! =null)}return this= =null || this.length == 0
}
Copy the code

If isNullOrEmpty() returns false, isNullOrEmpty! = null, so the variable s in printLength() will not be null.

Through contracts, developers can provide the compiler with the behavior of a function to help the compiler perform a more complete analysis of the code.

A contract is like a bridge between the developer and the compiler, but the compiler must abide by the contract unconditionally.

Contract

Contract is a way of notifying the compiler of the behavior of a function.

Contract is a new feature in Kotlin1.3 and is still experimental as of the current Kotlin 1.4.

2. The characteristics of Contract

  • Use contracts only inside top-level functions, not on members and class functions.
  • The declaration called by Contract must be the first statement in the function body.
  • Currently, the Kotlin compiler does not validate contracts, so it is the developer’s responsibility to write a correct and reasonable Contract.

In Kotlin 1.4, there are two improvements to the Contract:

  • Support for implementing contracts using inline specialized functions
  • Kotlin 1.3 cannot add a Contract to a member function, and since Kotlin 1.4 supports adding a Contract to a member function of final type (of course, any member function may be overwritten, so it cannot be added).

The current Contract has two types:

  • Returns Contracts
  • CallInPlace Contracts

2.1 Returns Contracts

Returns Contracts indicates that the implies condition holds when a return Returns a value (for example, true, false, null).

Returns come in the following forms:

  • returns(true) implies
  • returns(false) implies
  • returns(null) implies
  • returns implies
  • returnsNotNull implies

The other types are well understood literally, but returns implies.

Kotlin’s requireNotNull() function is available in the following format:

@kotlin.internal.InlineOnly
public inline fun <T : Any> requireNotNull(value: T?).: T { contract { returns() implies (value ! =null)}return requireNotNull(value) { "Required value was null."}}@kotlin.internal.InlineOnly
public inline fun <T : Any> requireNotNull(value: T? , lazyMessage: () ->Any): T { contract { returns() implies (value ! =null)}if (value == null) {
        val message = lazyMessage()
        throw IllegalArgumentException(message.toString())
    } else {
        return value
    }
}

Copy the code

Contract () tells the compiler that value is not null if a call to the requireNotNull function returns normally and no exceptions are thrown.

Therefore, returns implies that the following condition holds when the function returns normally.

Contract improves Kotlin’s intelligent inference by declaring the relationship between the result of a function call and the value of the parameter passed.

2.2 CallInPlace Contracts

The Scope Function is used by Kotlin to use Scope Functions elegantly. Scope Functions

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) - >R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)}Copy the code

The callsInPlace in contract() tells the compiler that a lambda expression block is executed only once within a let function. After the let function is called, the block is no longer executed.

CallsInPlace () allows developers to provide time/location/frequency constraints on lambda expressions called.

InvocationKind in callsInPlace() is an enumeration class that contains the following enumeration values:

  • AT_MOST_ONCE: Function arguments will be called once or not at all.
  • EXACTLY_ONCE: Function arguments will be called only once.
  • AT_LEAST_ONCE: Function arguments will be called once or more.
  • UNKNOWN: A function parameter that can be called an UNKNOWN number of times.

Kotlin’s Scope Function all uses the Contracts above.

Contract source code parsing

Contract (); Contract ();

@ContractsDsl
@ExperimentalContracts
@InlineOnly
@SinceKotlin("1.3")
@Suppress("UNUSED_PARAMETER")
public inline fun contract(builder: ContractBuilder. () - >Unit){}Copy the code

Contract was constructed through ContractBuilder, and its source code is as follows:

@ContractsDsl
@ExperimentalContracts
@SinceKotlin("1.3")
public interface ContractBuilder {
    /** * Describes a situation when a function returns normally, without any exceptions thrown. * * Use [SimpleEffect.implies] function to describe a conditional effect that happens in such case. * */
    // @sample samples.contracts.returnsContract
    @ContractsDsl public fun returns(a): Returns

    /** * Describes a situation when a function returns normally with the specified return [value]. * * The possible values of [value] are limited to `true`, `false` or `null`. * * Use [SimpleEffect.implies] function to describe a conditional effect that happens in such case. * * /
    // @sample samples.contracts.returnsTrueContract
    // @sample samples.contracts.returnsFalseContract
    // @sample samples.contracts.returnsNullContract
    @ContractsDsl public fun returns(value: Any?).: Returns

    /** * Describes a situation when a function returns normally with any value that is not `null`. * * Use [SimpleEffect.implies] function to describe a conditional effect that happens in such case. * */
    // @sample samples.contracts.returnsNotNullContract
    @ContractsDsl public fun returnsNotNull(a): ReturnsNotNull

    /** * Specifies that the function parameter [lambda] is invoked in place. * * This contract specifies that: * 1. the function [lambda] can only be invoked during the call of the owner function, * and it won't be invoked after that owner function call is completed; * 2. _(optionally)_ the function [lambda] is invoked the amount of times specified by the [kind] parameter, * see the [InvocationKind] enum for possible values. * * A function declaring the `callsInPlace` effect must be _inline_. * */
    /* @sample samples.contracts.callsInPlaceAtMostOnceContract * @sample samples.contracts.callsInPlaceAtLeastOnceContract * @sample samples.contracts.callsInPlaceExactlyOnceContract * @sample samples.contracts.callsInPlaceUnknownContract */
    @ContractsDsl public fun <R> callsInPlace(lambda: Function<R>, kind: InvocationKind = InvocationKind.UNKNOWN): CallsInPlace
}
Copy the code

Returns (), returnsNotNull(), callsInPlace() returns, returnsNotNull, and callsInPlace objects, respectively. These objects eventually implement the Effect interface:

@ContractsDsl
@ExperimentalContracts
@SinceKotlin("1.3")
public interface Effect
Copy the code

Effect represents the Effect of a function call. Every time a function is called, all of its effects are invoked. The compiler collects all fired effects for its analysis.

Currently, Kotlin supports only four effects:

  • Returns: indicates that the function Returns successfully without raising an exception.
  • ReturnsNotNull: indicates that the function successfully returns a value that is not null.
  • ConditionalEffect: a combination of an effect and a Boolean expression, guaranteed true if the effect is triggered.
  • CallsInPlace: Represents a constraint on the location and number of calls to the passed lambda argument.

4. Summary

Contracts are a great tool for compiler analysis, and they are very helpful for writing cleaner, better code. When using Contract, remember that the compiler does not validate the Contract.