Nim 完全指南 / 13 错误处理
第 13 章:错误处理
13.1 异常(Exception)
Nim 的异常是引用类型,继承自 CatchableError 或 Defect:
# 抛出异常
proc divide(a, b: float): float =
if b == 0:
raise newException(DivByZeroDefect, "Cannot divide by zero")
return a / b
# 捕获异常
try:
echo divide(10, 2)
echo divide(10, 0)
except DivByZeroDefect:
echo "Cannot divide by zero!"
except CatchableError as e:
echo "Error: ", e.msg
finally:
echo "Cleanup complete"
13.1.1 异常层次
| 类型 | 说明 | 是否可捕获 |
|---|---|---|
Defect | 不可恢复错误 | ❌(默认) |
CatchableError | 可恢复错误 | ✅ |
IOError | I/O 错误 | ✅ |
ValueError | 值错误 | ✅ |
KeyError | 键不存在 | ✅ |
OverflowDefect | 溢出 | ❌ |
DivByZeroDefect | 除零 | ❌ |
# 自定义异常类型
type
AppError = object of CatchableError
AuthError = object of AppError
NotFoundError = object of AppError
ValidationError = object of AppError
proc authenticate(user: string) =
if user != "admin":
raise newException(AuthError, "Invalid credentials")
proc findUser(id: int) =
if id > 100:
raise newException(NotFoundError, "User not found: " & $id)
try:
authenticate("guest")
except AuthError as e:
echo "Auth failed: ", e.msg
except NotFoundError as e:
echo "Not found: ", e.msg
except AppError as e:
echo "App error: ", e.msg
13.1.2 异常链
try:
try:
raise newException(IOError, "File not found")
except IOError as e:
raise newException(ValueError, "Invalid config", e)
except ValueError as e:
echo "Error: ", e.msg
echo "Caused by: ", e.parent.msg
13.2 Option 类型
Option[T] 表示一个可能存在也可能不存在的值:
import std/options
# 创建 Option
let someValue = some(42)
let noValue = none[int]()
# 检查
echo someValue.isSome # true
echo someValue.isNone # false
echo noValue.isSome # false
echo noValue.isNone # true
# 获取值
echo someValue.get() # 42
# echo noValue.get() # 运行时错误!
# 安全获取
echo noValue.get(0) # 0(默认值)
# 链式操作
proc double(x: int): Option[int] =
if x < 1000: some(x * 2)
else: none(int)
let result = some(21).map(double)
echo result # some(some(42))... 需要 flatMap
# flatMap (get)
let flat = some(21).map(proc(x: int): int = x * 2)
echo flat # some(42)
# 使用 isSome 和 get 进行模式匹配
proc processValue(opt: Option[int]) =
if opt.isSome:
let v = opt.get()
echo "Got value: ", v
else:
echo "No value"
processValue(some(42)) # Got value: 42
processValue(none(int)) # No value
# filter
echo some(42).filter(proc(x: int): bool = x > 0) # some(42)
echo some(-1).filter(proc(x: int): bool = x > 0) # none(int)
13.3 Result 类型
Nim 2.0 引入了标准的 Result[T, E] 类型(在 std/results 模块中):
import std/results
# 创建 Result
proc parseAge(s: string): Result[int, string] =
let age = try:
parseInt(s)
except ValueError:
return err("Invalid number: " & s)
if age < 0 or age > 150:
return err("Age out of range: " & s)
ok(age)
let r1 = parseAge("25")
let r2 = parseAge("abc")
let r3 = parseAge("200")
echo r1.isOk # true
echo r1.get() # 25
echo r2.isErr # true
echo r2.error() # "Invalid number: abc"
# 安全解包
echo r1.get(0) # 25
echo r2.get(0) # 0
# 链式操作
let doubled = r1.map(proc(x: int): int = x * 2)
echo doubled.get() # 50
# 错误转换
let withDefault = r2.mapErr(proc(e: string): string = "Parse error: " & e)
echo withDefault.error() # "Parse error: Invalid number: abc"
13.4 错误处理策略对比
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 异常 | 不可预期的错误 | 代码简洁、自动传播 | 有运行时开销 |
| Option | 可能缺失的值 | 明确表达可能性 | 丢失错误信息 |
| Result | 需要错误信息的可能失败 | 类型安全、错误信息完整 | 需要处理每个结果 |
13.5 实战示例
🏢 场景:配置文件解析
import std/[options, json, strutils, os]
type
ConfigError = object of CatchableError
Config = object
host: string
port: int
debug: bool
proc parseConfig(path: string): Result[Config, string] =
if not fileExists(path):
return err("Config file not found: " & path)
let content = try:
readFile(path)
except IOError as e:
return err("Cannot read config: " & e.msg)
let json = try:
parseJson(content)
except JsonParsingError as e:
return err("Invalid JSON: " & e.msg)
if not json.hasKey("host"):
return err("Missing required field: host")
ok(Config(
host: json["host"].getStr(),
port: json{"port"}.getInt(8080),
debug: json{"debug"}.getBool(false)
))
# 使用
let cfg = parseConfig("config.json")
if cfg.isOk:
let c = cfg.get()
echo &"Server: {c.host}:{c.port}"
else:
echo "Error: ", cfg.error()
🏢 场景:链式错误处理
import std/results
type AppError = object
code: int
message: string
proc err(code: int, msg: string): Result[void, AppError] =
Result[void, AppError].err(AppError(code: code, message: msg))
proc ok(): Result[void, AppError] =
Result[void, AppError].ok()
proc validateEmail(email: string): Result[void, AppError] =
if "@" notin email:
return err(400, "Invalid email format")
ok()
proc validateAge(age: int): Result[void, AppError] =
if age < 0 or age > 150:
return err(400, "Invalid age: " & $age)
ok()
proc registerUser(email: string, age: int): Result[string, AppError] =
let emailCheck = validateEmail(email)
if emailCheck.isErr:
return Result[string, AppError].err(emailCheck.error())
let ageCheck = validateAge(age)
if ageCheck.isErr:
return Result[string, AppError].err(ageCheck.error())
Result[string, AppError].ok("User registered: " & email)
let r1 = registerUser("[email protected]", 25)
echo r1 # ok("User registered: [email protected]")
let r2 = registerUser("invalid-email", 25)
echo r2.error().message # "Invalid email format"
本章小结
| 机制 | 类型 | 用途 |
|---|---|---|
raise | 异常 | 抛出错误 |
try/except | 异常 | 捕获错误 |
Option[T] | 值 | 可选值 |
Result[T, E] | 值 | 可能失败的操作 |
练习
- 使用 Result 类型实现一个文件读取工具
- 实现自定义异常层次结构
- 使用 Option 类型处理可能为空的查询结果
- 将异常式 API 包装为 Result 式 API
扩展阅读
← 上一章:元编程 | 下一章:模块与包管理 →