Kotlin实现Rust风格的Result

Posted by zsh on December 4, 2021

前段时间看到rust的错误处理方式,觉得十分优雅,于是就想能不能用Kotlin模仿一个版本。先看原版

1
2
3
4
5
6
7
8
9
10
11
12
use std::fs::File;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("Problem opening the file: {:?}", error)
        },
    };
}

然后看看我用Kotlin实现的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
    val fileUtil = FileUtil()
    val result = fileUtil.openFile("error")
    val content = result match {
        OK { str ->
            str
        }
        Error { error ->
            throw error
        }
    }
    println(content)
}

在语法结构上看起来已经十分接近了,可惜的是最后返回的content是可用类型,在后续使用的时候必须带上!!或者?:操作符, 下面我们来看看如何通过Kotlin的语法实现这样的错误处理。 首先Kotlin没有match关键字,但是Kotlin有infix函数,可以在语法上形成rust这样的视觉效果。OK和Err分支操作的实现可以通过sealed class基类和子类完成,在返回KResult的函数中根据具体逻辑返回OK或者Err。 下面是具体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
sealed class KResult<T, E : Throwable> {
    fun isOk(): Boolean = this is OK
    fun isError(): Boolean = this is Error

    fun <T, E : Throwable> KResult<T, E>.OK(block: (T) -> T): T? {
        return block((this as OK).data)
    }

    fun <T, E : Throwable> KResult<T, E>.Error(block: (E) -> Unit): T? {
        this as Error
        block(this.error)
        return data
    }
}

class OK<T, E : Throwable>(val data: T): KResult<T, E>() {}

class Error<T, E : Throwable>(val data: T?, val error: E): KResult<T, E>() {}

infix fun<T, E: Throwable> KResult<T, E>.match(block: KResult<T, E>.() -> T): T? {
    if (this.isOk()) {
        return (this as OK).data
    }
    throw (this as Error).error
}

函数中的使用如下

1
2
3
4
fun openFile(fileName: String): KResult<String?, Throwable> {
    if (fileName == "error") return Error(null, IOException("io exception"))
    return OK("content")
}