You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何我的递归调用并非尾调用?如何改写为尾递归?

尾递归警告问题解答

1. 警告出现的原因

  • Kotlin的tailrec修饰符要求递归调用必须是函数的最后一个执行操作,只有这样编译器才能把递归优化成循环,避免栈溢出。
  • 你的代码里,所有retry的递归调用都嵌套在fold函数的onFailure Lambda回调内部,而且回调里还先执行了日志打印、delay(针对部分异常)等操作才调用retry。这种嵌套在Lambda里的递归不属于函数的直接尾位置,编译器无法识别为合法的尾递归,所以抛出警告。

2. 可以改写成尾递归函数

把原代码中runCatching+fold的逻辑换成try-catch结构,让递归调用直接成为函数的返回表达式,就能满足尾递归的要求。改写后的示例代码如下:

private tailrec suspend fun <T> retry(numberOfRetries: Int, block: suspend () -> T): Result<T> {
    return try {
        Timber.d("retry($numberOfRetries)")
        Result.success(block())
    } catch (throwable: Throwable) {
        when (throwable) {
            is TimeoutCancellationException -> {
                Timber.e(throwable, "Request ran into timeout - retrying immediately...")
                if (numberOfRetries > 0) {
                    retry(numberOfRetries - 1, block)
                } else {
                    Result.failure(throwable)
                }
            }
            is HttpException -> {
                if (throwable.code() == HttpStatusCode.NOT_FOUND_404) {
                    Timber.e(throwable, "Request returned 404 - don't retry!")
                    Result.failure(throwable)
                } else {
                    Timber.e(throwable, "Request returned some other error code - retrying in 3 seconds...")
                    if (numberOfRetries > 0) {
                        delay(DELAY_BEFORE_RETRY_MS)
                        retry(numberOfRetries - 1, block)
                    } else {
                        Result.failure(throwable)
                    }
                }
            }
            else -> {
                Timber.e(throwable, "Some other problem with request - retrying in 3 seconds...")
                if (numberOfRetries > 0) {
                    delay(DELAY_BEFORE_RETRY_MS)
                    retry(numberOfRetries - 1, block)
                } else {
                    Result.failure(throwable)
                }
            }
        }
    }
}
  • 这里用try-catch替代了原来的链式调用,所有retry递归调用都是直接作为catch块分支的返回值,最终成为整个函数的返回表达式,属于合法的尾位置,编译器会认可这是尾递归,警告也就会消失。

内容的提问来源于stack exchange,提问作者david.mihola

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 21:53:26