异常追踪

Nim 支持异常追踪。 raises 编译指示可以显式定义过程/迭代器/方法/转换器所允许抛出的异常。编译期会加以验证:

  1. proc p(what: bool) {.raises: [IOError, OSError].} =
  2. if what: raise newException(IOError, "IO")
  3. else: raise newException(OSError, "OS")

空的 raises 列表(raises: [])表示不允许抛出异常:

  1. proc p(): bool {.raises: [].} =
  2. try:
  3. unsafeCall()
  4. result = true
  5. except CatchableError:
  6. result = false

raises 列表也可以附加到过程类型上。这会影响类型兼容性:

  1. type
  2. Callback = proc (s: string) {.raises: [IOError].}
  3. var
  4. c: Callback
  5. proc p(x: string) =
  6. raise newException(OSError, "OS")
  7. c = p # type error

对于例程 p 来说,编译器使用推断规则来判断可能引发的异常的集合; 算法在 p 的调用图上运行:

  1. 对过程类型 T 的每个间接调用都假定产生 system.Exception (所有异常的基类),即任意异常都有可能,除非 T 拥有显式的 raises 列表。 不过,如果是以 f(…) 的形式调用并且 f 是当前分析的例程的参数,而且并被标记 .effectsOf: f,那么忽略它。 乐观地假定这类调用没有 effect。 第二条规则对这种情况有所补充。
  2. 当某过程类型的表达式 e 是作为过程 p 的标记为 .effectsOf 的参数传入的,对 e 的调用会被视为间接调用,它的 raises 列表会加入到 p 的 raises 列表。
  3. 所有对方法体未知(因为声明前置)的过程 q 的调用都会被看作抛出 system.Exception 除非 q 显式定义了 raises 列表。 importc 导入的过程,若没有显式声明 raises 列表,则默认视为 .raises: []。
  4. 方法 m 每一次调用都假定会抛出 system.Exception,除非显式声明了 raises 列表。
  5. 对于其他的调用,Nim 可以分析推断出确切的 raises 列表。
  6. 推断 p 的 raises 列表时,Nim 会考虑它里面的 raise 和 try 语句。

.raises: [] 异常追踪机制不追踪继承自 system.Defect 的异常。这样更能跟内置运算符保持一致。 下面的代码是合法的:

  1. proc mydiv(a, b): int {.raises: [].} =
  2. a div b # 会抛出 DivByZeroDefect 异常

同理,下面的代码也是合法的:

  1. proc mydiv(a, b): int {.raises: [].} =
  2. if b == 0: raise newException(DivByZeroDefect, "除数为 0")
  3. else: result = a div b

这是因为 DivByZeroDefect 继承自 Defect,再加上 --panics:on 选项 Defect 异常就变成了不可修复性错误。(自从 Nim 1.4 开始)