fails / try / catch
cpro 没有异常。会失败的函数把错误当作返回值交给调用方,调用方必须处理。
| 拼音 | 中文 | 英文 | 作用 |
|---|---|---|---|
kecuo | 可错 | fails | 标记函数可能失败 |
cuowu | 错误 | Err | 构造一个错误 |
changshi | 尝试 | try | 调用会失败的函数 |
buhuo | 捕获 | catch | 处理错误 |
sp_kecuo | 上抛_可错 | up_fails | 把错误交给上一层 |
声明会失败的函数
在返回类型后面写 kecuo。失败时用 cuowu(...) 返回错误,成功时照常 fanhui:
hs safe_div(a z64, b z64) z64 kecuo {
ruguo (b == 0) {
fanhui cuowu("除数不能为 0")
}
fanhui a /[z64] b
}函数 safe_div(a i64, b i64) i64 可错 {
如果 (b == 0) {
返回 错误("除数不能为 0")
}
返回 a /[i64] b
}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 块处理错误:
rukou() {
changshi {
bl r = safe_div(10, 2)
dayin("结果:", r) // 结果: 5
bl r2 = safe_div(1, 0) // 失败,跳到错误处理
dayin("这行不会执行")
} buhuo (e) {
dayin("出错了:", e.xinxi) // 出错了: 除数不能为 0
}
}入口() {
尝试 {
变量 r = safe_div(10, 2)
打印("结果:", r) // 结果: 5
变量 r2 = safe_div(1, 0) // 失败,跳到错误处理
打印("这行不会执行")
} 捕获 (e) {
打印("出错了:", e.msg) // 出错了: 除数不能为 0
}
}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.daima | e.code | z64(i64) | 错误码,给程序判断用 |
e.xinxi | e.msg | zifuchuan(string) | 错误消息,给人看 |
构造错误有两种写法,消息总是写在最后:
fanhui cuowu("配置文件不存在") // 错误码为 0
fanhui cuowu(1, "配置文件不存在") // 指定错误码返回 错误("配置文件不存在") // 错误码为 0
返回 错误(1, "配置文件不存在") // 指定错误码return Err("配置文件不存在") // 错误码为 0
return Err(1, "配置文件不存在") // 指定错误码错误码 0 表示"未分类"。错误码的含义由产生错误的模块自己定义,标准库会提供判断函数,例如文件模块的 fs.is_not_found(e.code),不要直接写数字比较。
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)
}
}
}函数 open_config(path string) string 可错 {
如果 (path ==[string] "") {
返回 错误(1, "路径为空")
}
返回 "配置内容"
}
入口() {
尝试 {
打印(open_config(""))
} 捕获 (e) {
如果 (e.code == 1) {
打印("请提供路径")
} 否则 {
打印(e.msg)
}
}
}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。失败时把错误原样返回给上一层,成功时取出正常值继续往下走:
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)
}
}函数 load_age(text string) i64 可错 {
变量 n = 上抛_可错 字符串_转_数字(text, i64)
如果 (n < 0) {
返回 错误("年龄不能为负")
}
返回 n
}
入口() {
尝试 {
打印(load_age("18")) // 18
打印(load_age("abc")) // 转换失败,进入错误处理
} 捕获 (e) {
打印(e.msg)
}
}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)
}
}它只是省掉了样板代码,等价于:
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
}函数 load_age(text string) i64 可错 {
变量 n 类型 i64 = 0
尝试 {
n = 字符串_转_数字(text, i64)
} 捕获 (e) {
返回 错误(e.code, e.msg)
}
如果 (n < 0) {
返回 错误("年龄不能为负")
}
返回 n
}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。
只能修饰会失败的调用,修饰普通函数会报错。
只能写在这四个位置:
bl x = sp_kecuo f() // 变量初值
x = sp_kecuo f() // 赋值
fanhui sp_kecuo f() // 返回
sp_kecuo f() // 单独一行,不要返回值变量 x = 上抛_可错 f() // 变量初值
x = 上抛_可错 f() // 赋值
返回 上抛_可错 f() // 返回
上抛_可错 f() // 单独一行,不要返回值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()) 会报错,要分成两句:
bl b = sp_kecuo g()
bl a = sp_kecuo f(b)变量 b = 上抛_可错 g()
变量 a = 上抛_可错 f(b)var b = up_fails g()
var a = up_fails f(b)不能写在 changshi 块里,否则会绕过这个块的 buhuo;写在 buhuo 块里可以。
在 buhuo 里改写错误
想在交给上一层之前改写错误,就在 buhuo 里自己 fanhui cuowu(...):
hs read_port(text zifuchuan) z64 kecuo {
changshi {
fanhui zifuchuan_zhuan_shuzi(text, z64)
} buhuo (e) {
fanhui cuowu(e.daima, "端口号格式错误: " +[zifuchuan] text)
}
}函数 read_port(text string) i64 可错 {
尝试 {
返回 字符串_转_数字(text, i64)
} 捕获 (e) {
返回 错误(e.code, "端口号格式错误: " +[string] text)
}
}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。
