强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Nim 完全指南 / 13 错误处理

第 13 章:错误处理

13.1 异常(Exception)

Nim 的异常是引用类型,继承自 CatchableErrorDefect

# 抛出异常
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可恢复错误
IOErrorI/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("user@example.com", 25)
echo r1  # ok("User registered: user@example.com")

let r2 = registerUser("invalid-email", 25)
echo r2.error().message  # "Invalid email format"

本章小结

机制类型用途
raise异常抛出错误
try/except异常捕获错误
Option[T]可选值
Result[T, E]可能失败的操作

练习

  1. 使用 Result 类型实现一个文件读取工具
  2. 实现自定义异常层次结构
  3. 使用 Option 类型处理可能为空的查询结果
  4. 将异常式 API 包装为 Result 式 API

扩展阅读


上一章:元编程 | 下一章:模块与包管理