Can't understand the Kotlin source code? Starting from the Contracts function~

foreword

Recently, a friend reported that because the source code is Kotlin, they cannot understand it. In fact, most of the time, if you can't understand the source code of Kotlin, it's probably because you don't know some specific syntax. Just as you can't understand the source code because you don't understand the design pattern~

for example

Take the isNullOrEmpty method commonly used in Kotlin as an example, the source code is as follows:

@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
    contract {
        returns(false) implies (this@isNullOrEmpty != null)
    }

    return this == null || this.length == 0
}

Huh? The code is very simple, but why can't I understand it? What the hell is contract, and what is implied? In fact, after you understand how to use the contract function, you can understand similar source code.

What are Contracts?

Contracts means contracts and contracts. It has been introduced since version 1.3 of Kotlin. Simply put, Contracts can be used to solve some functions that the compiler cannot complete.

So, what exactly is it for?

Let's define a similar function to detect whether an object is null, first define a User object, the code is as follows:

data class User(val name: String, val age: Int)

Then define a method to check whether the User object is empty, the code is as follows:

fun isEmpty(user: User?) {
    if (user == null) {
        throw IllegalArgumentException("is empty")
    }
}

Then we call it in the business method, the code is as follows:

fun work(user: User?){
    isEmpty(user = user)
    setText(user.name)
}

 At this time, this method cannot be compiled, and the compiler will remind you that user is a nullable object, you need to add "?.",

But from our code logic, we first call the isEmpty method. If the user object is null, an exception has been thrown in the isEmpty method, that is to say, it will not go to the setText method. In other words, if the code can execute to the setText line, then the user object must not be empty. So what to do now? This requires Contracts to appear.

How to use Contracts

The Contracts API generally looks like this:

fun A() {
    contract {

    }
}

The current main usage methods are returns, callsInPlace, etc. First, let's look at the usage scenarios of returns.

Returns Contracts

contract {
    returns($value) implies($condition)
}

returns are used to tell the editor that the condition condition is true when the return value is value. The current value can be a Boolean type or an empty type, and the conditional expression also needs to return a Boolean type.

For example, we now modify the isEmpty method, the code is as follows:

@ExperimentalContracts
fun isEmpty(user: User?) {
    contract {
        returns() implies (user != null)
    }
    if (user == null) {
        throw IllegalArgumentException("is empty")
    }
}

Here we tell the compiler through contract constraints that if the isEmpty function can return normally, it means that user is not empty. In other words, it means that when the user is empty, an exception is thrown, so the isEmpty function cannot return.

You will find that after modifying the isEmpty method, the work method no longer reports an error.

This is because we have told the compiler through the isEmpty method that if the code can be executed to setText, it means that the user object must not be empty. Since this function has always been an experimental API, the @ExperimentalContracts annotation is added here.

However, this API is already widely used in the Kotlin source code, so we don't have to worry about major changes in the future. Then let's look at the usage scenario of CallInPlace.

CallInPlace Contracts

CallInPlace is also widely used, such as the standard functions apply, also, etc. that we commonly use in Kotlin. Here, the apply function is taken as an example. The source code of the apply function is as follows:

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

From the source code, we can see that the callsInPlace method is used in it. callsInPlace can tell the editor that the block method will only be executed once. instead of 0 or more times. Of course, you can also set the method to execute at least once or at most once. This reader can try it for himself.

Contracts source code

First, let's look at the source code of the contract function, as follows:

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

Contract is an inline function that requires a ContractBuilder object, and then look at the source code of the ContractBuilder object, as follows:

@ContractsDsl
@ExperimentalContracts
@SinceKotlin("1.3")
public interface ContractBuilder {

    @ContractsDsl public fun returns(): Returns

    @ContractsDsl public fun returns(value: Any?): Returns

    @ContractsDsl public fun returnsNotNull(): ReturnsNotNull

    @ContractsDsl public fun <R> callsInPlace(lambda: Function<R>, kind: InvocationKind = InvocationKind.UNKNOWN): CallsInPlace
}

We can see that the returns method returns Returns, the callsInPlace method returns CallsInPlace, and the Returns object is the SimpleEffect interface that implements the self-interface Effect, and the CallsInPalce object is the Effect interface. The source code is as follows:

@ContractsDsl
@ExperimentalContracts
@SinceKotlin("1.3")
public interface SimpleEffect : Effect {

    @ContractsDsl
    @ExperimentalContracts
    public infix fun implies(booleanExpression: Boolean): ConditionalEffect
}

@ContractsDsl
@ExperimentalContracts
@SinceKotlin("1.3")
public interface CallsInPlace : Effect

It can be seen from the source code of SimpleEffect that it implements an infix function named implies to tell the compiler that booleanExpression will be established, so as to make an agreement with the compiler.

write at the end

In fact, the contract makes up for the shortcomings of intelligent transformation by reaching an agreement with the compiler.

There may be some things happening in the near future. To borrow a sentence from the "Bing Xin Shuo" boss today - 2022, good things will take a long time~

Guess you like

Origin blog.csdn.net/huangliniqng/article/details/125210444