未定义函数和未注册名称

最后一类错误关注的是当进程试图执行一个未定义的函数或者给一个未注册的名称发送消息时会发生什么。

调用未定义函数

如果进程尝试调用Mod:Func(Arg0,…,ArgN),而该函数未被定义,则该调用被转换为:

  1. error_handler:undefined_function(Mod, Func, [Arg0,...,ArgN])

假设模块error_handler已经被加载(标准发行版中预定义了error_handler模块)。error_handler模块可以被定义为程序7.4。

程序 7.4

  1. -module(error_handler).
  2. -export([undefined_function/3]).
  3.  
  4. undefined_function(Module, Func, Args) ->
  5. case code:is_loaded(Module) of
  6. {file,File} ->
  7. % the module is loaded but not the function
  8. io:format("error undefined function:~w ~w ~w",
  9. [Module, Func, Args]),
  10. exit({undefined_function,{Module,Func,Args}});
  11. false ->
  12. case code:load_file(Module) of
  13. {module, _} ->
  14. apply(Module, Func, Args);
  15. {error, _} ->
  16. io:format("error undefined module:~w",
  17. [Module]),
  18. exit({undefined_module, Module})
  19. end
  20. end.

如果模块Mod已经被加载,那么将导致一个运行时错误。如果模块尚未加载,那么首先尝试加载该模块,若加载成功,再尝试执行先前调用的函数。

模块code了解哪些模块已被加载,同时也负责代码加载。

自动加载

编译过的函数无需再显式地编译或“加载”相关模块即可直接用于后续的会话。模块中的导出函数被第一次调用时,该模块将(通过上述的机制)被自动加载。

要实现自动加载,必须满足两个条件:首先,包含Erlang模块的源码文件必须与模块同名(扩展名必须为.erl);其次,系统使用的默认搜索路径必须能定位到该未知模块。

向未注册名称发送消息

尝试向一个不存在的注册进程发送消息时会触发error_handler:unregistered_name(Name,Pid,Message)调用。其中Name是不存在的注册进程的名称,Pid是发送消息的进程标识,Message是发送给注册进程的消息。

自定义缺省行为

执行BIF process_flag(error_handler,MyMod)可以用模块MyMod替换默认的error_handler。这使得用户得以定义他们(私有)的错误处理器,用以处理针对未定义函数的调用以及以为注册进程名称为目标的消息发送。该功能仅对执行调用的进程自身有效。定义非标准的错误处理器时必须注意:如果你在替换标准错误处理器时犯了什么错误,系统可能会失控!

也可以通过加载一个新版本的error_handler模块来更改默认行为。这么做会影响到所有的进程(定义了私有错误处理器的进程出外),因此非常危险。

Catch和退出信号捕获

catch作用域内求值和捕获进程退出信号是两种完全不同的错误处理机制。退出信号的捕获影响的是一个进程从其他进程处收到EXIT信号时的动作。catch只影响当前进程中由catch保护的表达式的求值。

执行程序7.5里的tt:test()会创建一个进程,这个进程匹配N(它的值是1)和2。这会失败的,引发信号{'EXIT',Pid,badmatch}被发送到执行tt:test()并且正在等待一个信号的进程。如果这个进程没有正在捕获exits,它也会非正常终止。

程序 7.5

  1. -module(tt).
  2. -export([test/0, p/1]).
  3.  
  4. test() ->
  5. spawn_link(tt, p,[1]),
  6. receive
  7. X ->
  8. X
  9. end.
  10.  
  11. p(N) ->
  12. N = 2.

调用程序7.5中的tt:test()将创建一个以2N(值为1)作匹配的链接进程。这会失败,并导致信号{'EXIT',Pid,badmatch}被发送至调用tt:test()的进程,该进程正在等待消息。要是这个进程不捕获退出信号,它就会异常退出。

如果我们执行的不是tt:test()而是catchtt:test(),结果一样:catch作用域外的另一个进程会发生匹配失败。在spawn_link(tt,p,[1])之前加上process_flag(trap_exit,true)tt:test()就会将收到的{'EXIT',Pid,badmatch}信号转换为一条消息。

脚注

[1]这不是bug或未录入文档的功能!
[2]这个错误可能导致当前shell崩溃。如何避免这个错误是留给读者的练习。