从 Minecraft 插件开发认识 Kotlin Contract
用 Kotlin 来写 Minecraft 插件是让人爽到喝水太多想尿尿的。举个例子,你可以给 ItemStack 定义一个扩展方法 (Extension Function)。

我们不用继承该类或者使用张三李五变形金刚设计模式,也不用单独创建一个 XXXUtils.class 即可实现原始类的扩展。
现在有个需求,我们要兼容 Nullable ItemStack 的判断 (即 ItemStack?)。因为有的时候我们拿到的 ItemStack 是 Null,很多情况会导致这样,包括我们在箱子内 getSlot(integer) 拿物品和部分超级我的世界插件开发者用 Java 写返回值接口的时候不会表 @NotNull。
这也是很容易改写的:

但现在问题来了:

在 ItemStack?.isAir() 扩展方法中,我们进行了判空操作。当返回值为 false 时就足以确认 ItemStack 不为 null,但 Kotlin 的编译器不知道这一点,它无法从这个方法内得到这个信息,如果要想过编译我们必须使用 item!!.type 或 item?.type,这使用起来太痛苦了,比 LexBurner 释放忍术还要痛苦。
问题在于 Kotlin 的编译器不知道这一信息,那我们是否可以手动提供给它来帮助推断?我们可以:

这就是 Kotlin 的契约 (Contract),它在 1.3 版本当中作为实验特性被添加进来,还有几个变体但都大同小异,这里就不一一介绍。
还有一个契约被称为 callsInPlace,举个例子:

在上面的代码同样出现了 “编译器不知道更多信息” 导致编译时期出现错误的情况,以编译器的视角来看的话:
如果 block 这个参数在 run 方法没有被执行,就会出现 item 未初始化的情况。
如果 block 这个参数在 run 方法被执行了多次,item 就会被多次赋值,而 val 声明的变量是不允许多次赋值的。
使用 callsInPlace 让它能正常跑起来:

在 Kotlin 的标准库内也大量使用了 Contract,例如 let、also、run 等等,我在最近才认识学习了这个特性,作为一直写 Kotlin 的这丢人到顶了。
垃圾话
Kotlin 的 Contract 有点类似于莆田版 Refinement Type,用我这种笨蛋垃圾废物能理解的话就是说:
如果普通的类型指定了值的取值范围或集合(例如 Integer 为 [-2^31,2^31-1]),那么 Refinement Type 即可以看作指定这个集合的子集。用 Scala 的 refined 库举个例子:

i2 在赋值的时候数值并不满足 Positive (即 >= 0) 的 Predicate,所以在编译时抛出了错误。
目前仅有一些非常牛逼且冷门的语言支持了原生的 Refinement Type,例 F*,但例如像 Scala,Haskell 这类语言也有 Refinement Type 的第三方库,好像 TypeScript 也有,但我没了解过。
随着 K2 Compiler 的完工,Kotlin 官方有想重新设计 Contract 语法的意思。你可以在 https://github.com/Kotlin/KEEP/issues/139 看到一些迹象。
我觉得用 B 站专栏写技术文章的人用 Java 写返回值接口的时候不会表 @NotNull 的人一样抽象,但这篇文章很水不想挂博客和知乎所以我挂在这里。