Kotlin Contract

简介: Kotlin Contract

Kotlin 的智能推断是其语言的一大特色。


智能推断,能够根据类型检测自动转换类型。


但是,智能推断并没有想象中的强大,例如下面的代码就无法进行推断,导致编译失败:

fun String?.isNotNull():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?
    }
}


因为编译器在处理s.length时,会将 s 推断成value-parameter s: String? = ...并不是 String 类型。智能推断失效了,代码也无法编译。


对上述代码做如下修改,即可编译成功:

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


isNullOrEmpty() 是 Kotlin 标准库中 String 的扩展函数,其源码:

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


我们会发现 isNullOrEmpty() 的源码中包含了contract函数,实际上它会告诉编译器当 isNullOrEmpty() 返回 false 时,则 isNullOrEmpty != null 成立,因此 printLength() 函数中的变量 s 不会为 null。


通过契约,开发者可以向编译器提供有关函数的行为,以帮助编译器对代码执行更完整的分析。


契约就像是开发者和编译器沟通的桥梁,但是编译器必须无条件地遵守契约。


一. Contract 的概念



Contract 是一种向编译器通知函数行为的方法。

Contract 是 Kotlin1.3 的新特性,在当前 Kotlin 1.4 时仍处于试验阶段。


二. Contract 的特性



  • 只能在 top-level 函数体内使用 Contract,不能在成员和类函数上使用它们。
  • Contract 所调用的声明必须是函数体内第一条语句。
  • 目前 Kotlin 编译器并不会验证 Contract,因此开发者有责任编写正确合理的 Contract。


在 Kotlin 1.4 中,对于 Contract 有两项改进:


  • 支持使用内联特化的函数来实现契约
  • Kotlin 1.3 不能为成员函数添加 Contract,从 Kotlin 1.4 开始支持为 final 类型的成员函数添加 Contract(当然任意成员函数可能存在被覆写的问题,因而不能添加)。


当前 Contract 有两种类型:


  • Returns Contracts
  • CallInPlace Contracts


2.1 Returns Contracts


Returns Contracts 表示当 return 的返回值是某个值(例如true、false、null)时,implies后面的条件成立。


Returns Contracts 有以下几种形式:


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


其他几个类型按照字面意思很好理解,returns implies 怎么理解呢?


我们来看一下 Kotlin 的 requireNotNull() 函数的源码:

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


contract() 告诉编译器,如果调用 requireNotNull 函数后能够正常返回,且没有抛出异常,则 value 不为空。


因此,returns implies  表示当该函数正常返回时,implies后面的条件成立。


Contract 正是通过这种声明函数调用的结果与所传参数值之间的关系来改进 Kotlin 智能推断的效果。


2.2 CallInPlace Contracts


前面Kotlin 如何优雅地使用 Scope Functions曾介绍过 Scope Function,我们来回顾一下 let 函数的源码:

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


contract() 中的 callsInPlace 会告知编译器,lambda 表达式 block 在 let 函数内只会执行一次。在 let 函数被调用结束后,block 将不再被执行。


callsInPlace() 允许开发者提供对调用的 lambda 表达式进行时间/位置/频率上的约束。


callsInPlace() 中的 InvocationKind 是一个枚举类,包含如下的枚举值:


  • AT_MOST_ONCE:函数参数将被调用一次或根本不调用。
  • EXACTLY_ONCE:函数参数将只被调用一次。
  • AT_LEAST_ONCE:函数参数将被调用一次或多次。
  • UNKNOWN:一个函数参数它可以被调用的次数未知。


Kotlin 的 Scope Function 都使用了上述 Contracts。


三. Contract 源码解析



Contract 采用 DSL 方式进行声明,我们来看一下 contract() 函数的源码:

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


通过  ContractBuilder 构建了 Contract,其源码如下:

@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(): 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(): 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
}


returns()、returnsNotNull()、callsInPlace() 分别返回 Returns、ReturnsNotNull、CallsInPlace 对象。这些对象最终都实现了 Effect 接口:

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


Effect 表示函数调用的效果。每当调用一个函数时,它的所有效果都会被激发。编译器将收集所有激发的效果以便于其分析。


目前 Kotlin 只支持有 4 种 Effect:


  • Returns: 表示函数成功返回,不会不引发异常。
  • ReturnsNotNull:表示函数成功返回不为 null 的值。
  • ConditionalEffect:表示一个效果和一个布尔表达式的组合,如果触发了效果,则保证为true。
  • CallsInPlace:表示对传递的 lambda 参数的调用位置和调用次数的约束。


四. 小结



Contract 是帮助编译器分析的一个很好的工具,它们对于编写更干净、更好的代码非常有帮助。在使用 Contract 的时候,请不要忘记编译器不会去验证 Contract。

相关文章
|
存储 编解码 Windows
AVI 格式的历史和演变
AVI 格式的主要功能使其成为存储和播放多媒体内容的多功能且实用的选择。 它对多种编解码器、各种分辨率和广泛兼容性的支持使 AVI 格式成为视频爱好者和专业人士的可靠选择。
1108 0
|
7月前
|
人工智能 安全 测试技术
大模型时代,断言还管用吗?AI 系统测试的结构性变革
本文探讨AI系统测试的范式变革:大模型、RAG与Agent等新型系统具有概率性、黑盒性与非确定性,使传统“输入→输出→断言”模式失效。测试需从功能验证转向质量评估,构建分层模型与量化指标体系,测试工程师正升级为概率系统评测体系的设计者。
|
7月前
|
存储 Prometheus 监控
Prometheus+Grafana构建企业级监控方案
Prometheus是一种开源的监控系统,通过时间序列数据库存储指标数据,支持多维数据模型和PromQL查询语言。其工作原理是通过HTTP拉取应用暴露的指标(如SpringBoot的Actuator端点),并持久化存储。示例展示了SpringBoot整合Prometheus的过程,包括依赖引入、配置暴露指标端点,以及通过Docker部署应用。最后介绍了Prometheus与Grafana的集成,通过配置数据源和仪表板实现可视化监控。整个方案适用于内网部署,支持服务发现和多种中间件监控。
|
4月前
|
人工智能 自然语言处理 搜索推荐
品牌在生成式AI中的可见性如何测量?——从DeepSeek到豆包的跨平台监测方法论
生成式AI正重塑用户获取品牌信息的方式。本文提出面向DeepSeek、豆包等对话式AI的品牌监测新框架,聚焦三大核心指标(提及率、推荐率、引用率)与四大辅助指标,通过标准化问题集、去个性化采样及跨平台归一化,实现可复现、可比对的AI心智评估。(239字)
453 2
|
9月前
|
存储 弹性计算 人工智能
2026年阿里云服务器租用价格表整理:配置体系、收费标准与优惠政策
阿里云服务器租用价格覆盖从 38 元 / 年的轻量配置到数万元 / 年的高性能实例,用户需根据业务负载(并发量、算力需求)与使用周期选择:个人 / 小微团队优先轻量服务器或经济型 e 实例,中小企业核心业务选通用算力型 u1 实例,高负载场景适配第八代高性能实例,AI / 渲染需求选择 GPU 服务器。选型时需结合优惠政策(如续费同价、长期折扣)与附加成本(带宽、存储),通过阿里云官方价格计算器精准核算,确保配置与需求匹配,实现性能与成本的平衡。
|
机器学习/深度学习 缓存 并行计算
90_推理优化:性能调优技术
随着大型语言模型(LLM)规模的不断扩大和应用场景的日益复杂,推理性能已成为制约模型实际部署和应用的关键因素。尽管大模型在各项任务上展现出了令人惊艳的能力,但其庞大的参数量和计算需求也带来了严峻的性能挑战。在资源受限的环境中,如何在保持模型效果的同时,最大化推理性能,成为了研究人员和工程师们亟待解决的核心问题。
743 0
|
存储 安全 数据可视化
如何开发人事管理系统中的薪酬管理板块?(附架构图+流程图+代码参考)
本文深入解析了企业薪酬管理模块的开发与实现,涵盖模块概述、核心功能、业务流程及开发技巧。通过实际代码示例,帮助开发人员构建高效、灵活的薪酬管理系统,提升企业人事管理效率与数据准确性。
|
机器学习/深度学习 人工智能 算法
AI浪潮下的青年觉醒:生成式人工智能(GAI)认证赋能未来竞争力与人文担当
人工智能正深刻改变生活与工作,青年面临前所未有的机遇与挑战。文章探讨了青年在AI时代的觉醒、核心竞争力的培养及技术伦理参与的重要性,并强调生成式人工智能(GAI)认证的意义,助力青年提升技能与就业竞争力,推动科学教育与技术创新融合。青年应保持好奇心、坚持人文关怀,引领未来社会发展方向,在AI浪潮中创造价值与美好未来。
|
NoSQL Shell MongoDB
Mac OSX 平台安装 MongoDB
10月更文挑战第11天
736 4
|
JavaScript
vue 滑动拼图验证
vue 滑动拼图验证
507 1
vue 滑动拼图验证

热门文章

最新文章