Skip to content

fails / try / catch ​

cpro 没有异常。会失败的函数把错误当作返回值交给调用方,调用方必须处理。

拼音中文英文作用
kecuo可错fails标记函数可能失败
cuowu错误Err构造一个错误
changshi尝试try调用会失败的函数
buhuo捕获catch处理错误
sp_kecuo上抛_可错up_fails把错误交给上一层

声明会失败的函数 ​

在返回类型后面写 kecuo。失败时用 cuowu(...) 返回错误,成功时照常 fanhui:

cpro
hs safe_div(a z64, b z64) z64 kecuo {
    ruguo (b == 0) {
        fanhui cuowu("除数不能为 0")
    }
    fanhui a /[z64] b
}
cpro
函数 safe_div(a i64, b i64) i64 可错 {
    如果 (b == 0) {
        返回 错误("除数不能为 0")
    }
    返回 a /[i64] b
}
cpro
fn safe_div(a i64, b i64) i64 fails {
    if (b == 0) {
        return Err("除数不能为 0")
    }
    return a /[i64] b
}
  • kecuo 必须写在返回类型后面
  • 必须有具体的返回类型,不支持 kongbai(void)加 kecuo
  • 成功时直接返回值,不需要额外包装

调用:changshi / buhuo ​

调用会失败的函数,必须写在 changshi 块里,并跟一个 buhuo 块处理错误:

cpro
rukou() {
    changshi {
        bl r = safe_div(10, 2)
        dayin("结果:", r)               // 结果: 5
        bl r2 = safe_div(1, 0)          // 失败,跳到错误处理
        dayin("这行不会执行")
    } buhuo (e) {
        dayin("出错了:", e.xinxi)       // 出错了: 除数不能为 0
    }
}
cpro
入口() {
    尝试 {
        变量 r = safe_div(10, 2)
        打印("结果:", r)                // 结果: 5
        变量 r2 = safe_div(1, 0)        // 失败,跳到错误处理
        打印("这行不会执行")
    } 捕获 (e) {
        打印("出错了:", e.msg)          // 出错了: 除数不能为 0
    }
}
cpro
rukou() {
    try {
        var r = safe_div(10, 2)
        print("结果:", r)               // 结果: 5
        var r2 = safe_div(1, 0)         // 失败,跳到错误处理
        print("这行不会执行")
    } catch (e) {
        print("出错了:", e.msg)         // 出错了: 除数不能为 0
    }
}
  • 成功时拿到的就是正常的值,上例中 r 是 z64(i64)
  • 失败时立即跳到 buhuo,changshi 里后面的语句不再执行
  • buhuo 后面必须写错误变量名,如 (e)
  • changshi 块里至少要有一个会失败的调用

错误对象 ​

错误变量有两个字段:

拼音英文类型说明
e.daimae.codez64(i64)错误码,给程序判断用
e.xinxie.msgzifuchuan(string)错误消息,给人看

构造错误有两种写法,消息总是写在最后:

cpro
fanhui cuowu("配置文件不存在")          // 错误码为 0
fanhui cuowu(1, "配置文件不存在")       // 指定错误码
cpro
返回 错误("配置文件不存在")             // 错误码为 0
返回 错误(1, "配置文件不存在")          // 指定错误码
cpro
return Err("配置文件不存在")            // 错误码为 0
return Err(1, "配置文件不存在")         // 指定错误码

错误码 0 表示"未分类"。错误码的含义由产生错误的模块自己定义,标准库会提供判断函数,例如文件模块的 fs.is_not_found(e.code),不要直接写数字比较。

cpro
hs open_config(path zifuchuan) zifuchuan kecuo {
    ruguo (path ==[zifuchuan] "") {
        fanhui cuowu(1, "路径为空")
    }
    fanhui "配置内容"
}

rukou() {
    changshi {
        dayin(open_config(""))
    } buhuo (e) {
        ruguo (e.daima == 1) {
            dayin("请提供路径")
        } fouze {
            dayin(e.xinxi)
        }
    }
}
cpro
函数 open_config(path string) string 可错 {
    如果 (path ==[string] "") {
        返回 错误(1, "路径为空")
    }
    返回 "配置内容"
}

入口() {
    尝试 {
        打印(open_config(""))
    } 捕获 (e) {
        如果 (e.code == 1) {
            打印("请提供路径")
        } 否则 {
            打印(e.msg)
        }
    }
}
cpro
fn open_config(path string) string fails {
    if (path ==[string] "") {
        return Err(1, "路径为空")
    }
    return "配置内容"
}

rukou() {
    try {
        print(open_config(""))
    } catch (e) {
        if (e.code == 1) {
            print("请提供路径")
        } else {
            print(e.msg)
        }
    }
}

把错误交给上一层:sp_kecuo ​

当前函数不想处理、要交给调用方时,在调用前面写 sp_kecuo。失败时把错误原样返回给上一层,成功时取出正常值继续往下走:

cpro
hs load_age(text zifuchuan) z64 kecuo {
    bl n = sp_kecuo zifuchuan_zhuan_shuzi(text, z64)
    ruguo (n < 0) {
        fanhui cuowu("年龄不能为负")
    }
    fanhui n
}

rukou() {
    changshi {
        dayin(load_age("18"))           // 18
        dayin(load_age("abc"))          // 转换失败,进入错误处理
    } buhuo (e) {
        dayin(e.xinxi)
    }
}
cpro
函数 load_age(text string) i64 可错 {
    变量 n = 上抛_可错 字符串_转_数字(text, i64)
    如果 (n < 0) {
        返回 错误("年龄不能为负")
    }
    返回 n
}

入口() {
    尝试 {
        打印(load_age("18"))            // 18
        打印(load_age("abc"))           // 转换失败,进入错误处理
    } 捕获 (e) {
        打印(e.msg)
    }
}
cpro
fn load_age(text string) i64 fails {
    var n = up_fails string_to_number(text, i64)
    if (n < 0) {
        return Err("年龄不能为负")
    }
    return n
}

rukou() {
    try {
        print(load_age("18"))           // 18
        print(load_age("abc"))          // 转换失败,进入错误处理
    } catch (e) {
        print(e.msg)
    }
}

它只是省掉了样板代码,等价于:

cpro
hs load_age(text zifuchuan) z64 kecuo {
    bl n leixing z64 = 0
    changshi {
        n = zifuchuan_zhuan_shuzi(text, z64)
    } buhuo (e) {
        fanhui cuowu(e.daima, e.xinxi)
    }
    ruguo (n < 0) {
        fanhui cuowu("年龄不能为负")
    }
    fanhui n
}
cpro
函数 load_age(text string) i64 可错 {
    变量 n 类型 i64 = 0
    尝试 {
        n = 字符串_转_数字(text, i64)
    } 捕获 (e) {
        返回 错误(e.code, e.msg)
    }
    如果 (n < 0) {
        返回 错误("年龄不能为负")
    }
    返回 n
}
cpro
fn load_age(text string) i64 fails {
    var n type i64 = 0
    try {
        n = string_to_number(text, i64)
    } catch (e) {
        return Err(e.code, e.msg)
    }
    if (n < 0) {
        return Err("年龄不能为负")
    }
    return n
}

sp_kecuo 的规则 ​

只能用在 kecuo 函数里。普通函数(包括 rukou)没有地方交出错误,请用 changshi / buhuo。

只能修饰会失败的调用,修饰普通函数会报错。

只能写在这四个位置:

cpro
bl x = sp_kecuo f()       // 变量初值
x = sp_kecuo f()          // 赋值
fanhui sp_kecuo f()       // 返回
sp_kecuo f()              // 单独一行,不要返回值
cpro
变量 x = 上抛_可错 f()    // 变量初值
x = 上抛_可错 f()         // 赋值
返回 上抛_可错 f()        // 返回
上抛_可错 f()             // 单独一行,不要返回值
cpro
var x = up_fails f()      // 变量初值
x = up_fails f()          // 赋值
return up_fails f()       // 返回
up_fails f()              // 单独一行,不要返回值

写在表达式中间(如 g(sp_kecuo f())、sp_kecuo f() + 1)会报错,请先存到变量里。

一条语句只能有一个会失败的调用。f 和 g 都会失败时,sp_kecuo f(g()) 会报错,要分成两句:

cpro
bl b = sp_kecuo g()
bl a = sp_kecuo f(b)
cpro
变量 b = 上抛_可错 g()
变量 a = 上抛_可错 f(b)
cpro
var b = up_fails g()
var a = up_fails f(b)

不能写在 changshi 块里,否则会绕过这个块的 buhuo;写在 buhuo 块里可以。

在 buhuo 里改写错误 ​

想在交给上一层之前改写错误,就在 buhuo 里自己 fanhui cuowu(...):

cpro
hs read_port(text zifuchuan) z64 kecuo {
    changshi {
        fanhui zifuchuan_zhuan_shuzi(text, z64)
    } buhuo (e) {
        fanhui cuowu(e.daima, "端口号格式错误: " +[zifuchuan] text)
    }
}
cpro
函数 read_port(text string) i64 可错 {
    尝试 {
        返回 字符串_转_数字(text, i64)
    } 捕获 (e) {
        返回 错误(e.code, "端口号格式错误: " +[string] text)
    }
}
cpro
fn read_port(text string) i64 fails {
    try {
        return string_to_number(text, i64)
    } catch (e) {
        return Err(e.code, "端口号格式错误: " +[string] text)
    }
}

注意

转发时写成单参数的 cuowu(e.xinxi),错误码会变成 0,上一层就没法按错误码判断了。要保留错误码,请写 cuowu(e.daima, ...)。sp_kecuo 总是原样保留错误码和消息。

该用哪个 ​

场景写法
当前函数不关心,交给调用方sp_kecuo
就地处理:打印、给默认值、换一种做法changshi / buhuo
交给调用方之前要改写错误码或消息在 buhuo 里 fanhui cuowu(...)
当前函数不是 kecuo 函数(如 rukou)changshi / buhuo

通常的写法是:中间各层用 sp_kecuo 往上传,最外层(一般是 rukou)用一个 changshi / buhuo 统一处理。

错误不会自己往上走,也没有栈展开:每个可能提前返回的地方都写着 sp_kecuo 或 fanhui,读代码一眼就能看到。

程序有 bug、无法继续运行的情况不走这一套,见 panic。

cpro 目前处于测试阶段