新闻详情

新闻详情

首页 / 资讯中心 / 详情

Elixir 错误处理完全指南:深入理解 try、catch 与 rescue 三大机制

发布时间:2026/10/1 9:42:39来源:尧图网络
Elixir 错误处理完全指南:深入理解 try、catch 与 rescue 三大机制
编程语言编译器标准库语言运行时并发编程【免费下载链接】elixirSimple from zero to scale项目地址https://gitcode.com/GitHub_Trending/el/elixir点击查看免费下载Elixir 提供了三种相互独立的错误处理机制errors异常、throws抛出/捕获与 exits退出信号它们分别服务于不同场景。本文将基于官方入门指南系统讲解三者的语义、使用时机与实战代码并结合当前仓库源码剖析raise、defexception、File.read!/1、Exception.format/3等底层实现帮助你掌握何时该 rescue、何时该 let it crash的 Elixir 设计哲学。Elixir 的三种错误机制概览Elixir 将程序运行中的异常情况划分为三类各有各的适用边界机制触发方式捕获方式典型用途Errors异常raise、算术错误等try/rescue、reraise表达非预期、异常的情况Throws抛出throwtry/catch无法用常规返回值表达时的流程控制Exits退出exit、进程崩溃try/catch的:exit子句进程间通信、监督树重启三者中errors 是最常用的机制而 throws 与 exits 的使用频率远低于其他语言。下文逐一展开。Errors异常用 raise 表达非预期情况从内建错误说起当代码执行了非法操作时VM 会抛出内建异常。例如给原子atom加数字iex :foo 1 ** (ArithmeticError) bad argument in arithmetic expression :erlang.(:foo, 1)使用 raise/1 与 raise/2任何时刻都可以用raise/1主动抛出一个运行时错误iex raise oops ** (RuntimeError) oops也可以传入错误模块名和关键字参数列表使用raise/2抛出特定类型的异常iex raise ArgumentError, message: invalid argument foo ** (ArgumentError) invalid argument foo从源码层面看raise的实现位于 lib/elixir/lib/kernel.ex。其宏展开逻辑为当参数是字符串binary时构造RuntimeError.exception(message)并调用:erlang.error/3当参数是原子即异常模块名时调用alias.exception([])其他情况则交给Kernel.Utils.raise/1处理。而raise/2则直接展开为:erlang.error(exception.exception(attributes))见 kernel.ex——也就是说任何模块只要实现了exception/1回调就能被raise使用。用 defexception/1 定义自定义异常定义自己的异常只需创建一个模块并在其中调用defexception/1。最常见的做法是定义一个带message字段的异常iex defmodule MyError do iex defexception message: default message iex end iex raise MyError ** (MyError) default message iex raise MyError, message: custom message ** (MyError) custom messagedefexception/1宏见 kernel.ex实际做的是为模块声明behaviour Exception用defstruct([__exception__: true] fields)生成结构体并自动实现Exception行为所需的message/1与exception/1回调。其中exception/1支持两种入参二进制字符串转为message字段和关键字列表通过struct!/2填充结构体并且两者都声明为可覆写defoverridable方便你自定义异常消息的生成逻辑。try/rescue捕获异常用try/rescue结构可以捕获异常iex try do ... raise oops ... rescue ... e in RuntimeError - e ... end %RuntimeError{message: oops}上面的例子把运行时错误捕获下来并原样返回异常结构体。如果不需要访问异常本身可以省略变量iex try do ... raise oops ... rescue ... RuntimeError - Error! ... end Error!为什么不鼓励滥用 try/rescue在实际的 Elixir 开发中try/rescue很少出现。以文件读取为例许多语言会强制你在文件打不开时 rescue 异常而 Elixir 提供了返回元组的File.read/1iex File.read(hello) {:error, :enoent} iex File.write(hello, world) :ok iex File.read(hello) {:ok, world}配合case做模式匹配即可优雅处理多种结果iex case File.read(hello) do ... {:ok, body} - IO.puts(Success: #{body}) ... {:error, reason} - IO.puts(Error: #{reason}) ... end而当文件理应存在、缺失意味着真正出错了时才使用File.read!/1iex File.read!(unknown) ** (File.Error) could not read file unknown: no such file or directory (elixir) lib/file.ex:272: File.read!/1查看 lib/elixir/lib/file.ex 的源码可以清晰地看到这套约定File.read/2直接调用:file.read_file/2返回{:ok, binary} | {:error, reason}而File.read!/2内部先调用read/2成功时直接返回 binary失败时则raise File.Error, reason: reason, action: read file, path: ...。这是 Elixir 标准库普遍遵循的约定foo返回元组foo!在出错时抛异常、成功时返回裸结果。最终文件打开失败是否属于异常完全由你的应用语义决定。Fail fast / Let it crash让异常自然发生Erlang 与 Elixir 社区有一句名言fail fast / let it crash。其核心思想是当非预期的事情发生时最好让异常自然抛出而不是把它 rescue 掉。关键在于强调非预期这个词。比如你写一个处理文件的脚本用户可能输入不存在的文件名——这属于预期内的失败用File.read/1并给用户清晰反馈显然比File.read!/1直接崩溃更合理。反之当某个文件理应存在、缺失说明系统其他环节出了严重问题时File.read!/1就是正确的选择。let it crash 之所以成立离不开进程隔离正如 进程Processes 一章所述Elixir 代码都运行在相互隔离、默认不共享任何状态的进程里。一个进程中未处理的异常绝不会崩溃或破坏另一个进程的状态。因此可以定义监督进程supervisor来观察某个进程是否意外终止并在其位置启动一个新进程。归根结底fail fast 意味着当非预期情况发生时与其在没有完整上下文的情况下盲目 rescue 所有可能的错误不如让监督者启动一个全新的进程从头开始。Reraise观测场景下的记录并重抛尽管一般避免使用try/rescue但在可观测性/监控场景下它很有价值。比如想记录出错了可以这样try do ... some code ... rescue e - Logger.error(Exception.format(:error, e, __STACKTRACE__)) reraise e, __STACKTRACE__ end这里捕获异常后先记录日志再原样重抛。关键在于__STACKTRACE__既用于格式化异常也用于重抛从而保证异常的值与来源都不被改变。源码佐证Exception.format/3见 lib/elixir/lib/exception.ex通过format_banner/3生成如** (RuntimeError) oops这样的横幅再拼接format_stacktrace/1的栈帧信息。而reraise/2见 kernel.ex与raise的宏展开逻辑一致区别在于调用:erlang.raise(:error, exception, stacktrace)时显式传入了原有栈不会生成新栈。总结一句Elixir 对 errors 采取字面意义的理解——它们只用于非预期/异常的情况绝不用于控制代码的正常流程。若确实需要流程控制应使用 throws。Throws用于非常规流程控制throw与catch保留给不通过 throw/catch 就无法取回值的少见场景。典型例子是假设Enum模块没有提供查找值的 API你需要在一个数字列表中找出第一个 13 的倍数iex try do ... Enum.each(-50..50, fn x - ... if rem(x, 13) 0, do: throw(x) ... end) ... Got nothing ... catch ... x - Got #{x} ... end Got -39而实际上Enum提供了完善的 API直接使用Enum.find/2才是正解iex Enum.find(-50..50, (rem(1, 13) 0)) -39Enum.find/3的实现见 lib/elixir/lib/enum.ex内部基于Enumerable.reduce/3协议一旦谓词命中便以{:halt, entry}提前终止遍历——这正是用协议和返回值表达而不是用 throw的体现。实践中throws 主要出现在与未提供恰当 API 的库对接时使用频率很低。Exits进程退出信号所有 Elixir 代码都运行在相互通信的进程中。当进程因自然原因如未处理的异常死亡时会发送一个exit信号进程也可以显式发送退出信号iex spawn_link(fn - exit(1) end) ** (EXIT from #PID0.56.0) shell process exited with reason: 1上例中链接进程以值1发送退出信号Elixir shell 自动处理该消息并打印到终端。exit/1的源码见 kernel.ex非常简洁只是对:erlang.exit(reason)的一层封装。exit也可以通过try/catch捕获iex try do ... exit(I am exiting) ... catch ... :exit, _ - not really ... end not reallycatch甚至可以脱离try单独出现在函数体中defmodule Example do def matched_catch do exit(:timeout) catch :exit, :timeout - {:error, :timeout} end def mismatched_catch do exit(:timeout) catch # Since no clause matches, this catch will have no effect :exit, :explosion - {:error, :explosion} end end不过try/catch本身已不常用用它来捕获 exit 更是罕见。exit信号是 Erlang VM 容错系统的关键部分进程通常运行在监督树supervision tree下监督进程本身也是进程负责监听被监督进程的exit信号一旦收到信号监督策略便会触发并重启该进程。正是这套监督系统让try/catch和try/rescue在 Elixir 中显得如此少见——与其 rescue 错误不如fail fast因为监督树会保证应用在出错后回到已知的初始状态。After无论成败都执行清理当某个可能抛错的动作之后必须清理资源时try/after结构派上用场。例如打开文件后用after子句保证关闭——即使中途出错iex {:ok, file} File.open(sample, [:utf8, :write]) iex try do ... IO.write(file, olá) ... raise oops, something went wrong ... after ... File.close(file) ... end ** (RuntimeError) oops, something went wrongafter子句无论try块是否成功都会执行。但需要注意如果链接的进程退出当前进程会随之退出after子句将不会运行——因此after只提供软保证。好在 Elixir 中的文件同样与当前进程链接即使进程崩溃文件也会被关闭ETS 表、socket、端口等资源同理。有时你想把整个函数体包进try以保证某段代码必定执行。此时 Elixir 允许省略try关键字iex defmodule RunAfter do ... def without_even_trying do ... raise oops ... after ... IO.puts(cleaning up!) ... end ... end iex RunAfter.without_even_trying cleaning up! ** (RuntimeError) oops只要函数体中出现after、rescue或catch之一Elixir 就会自动将函数体包进try。after块只负责副作用不会改变其上各子句的返回值。Elsetry 成功时的模式匹配分支当try块没有抛出异常或 throw、正常结束时else块会对try的结果进行模式匹配iex x 2 2 iex try do ... 1 / x ... rescue ... ArithmeticError - ... :infinity ... else ... y when y 1 and y -1 - ... :small ... _ - ... :large ... end :small注意两条边界规则else块中抛出的异常不会被捕获如果else块中没有任何模式匹配成功会抛出异常且该异常不会被当前的try/catch/rescue/after块捕获。变量作用域try 块内不向外泄漏与case、cond、if等结构一致try/catch/rescue/after块内部定义的变量不会泄漏到外层上下文。以下代码是非法的iex try do ... raise fail ... what_happened :did_not_raise ... rescue ... _ - what_happened :rescued ... end iex what_happened ** (CompileError) undefined variable what_happened正确的做法是把try表达式的结果作为返回值赋给外层变量iex what_happened ... try do ... raise fail ... :did_not_raise ... rescue ... _ - :rescued ... end iex what_happened :rescued更进一步try的 do 块中定义的变量在rescue/after/else中同样不可用——因为try块可能在任意时刻失败变量可能从未被绑定过iex try do ... raise fail ... another_what_happened :did_not_raise ... rescue ... _ - another_what_happened ... end ** (CompileError) undefined variable another_what_happened总结Elixir 的try、catch、rescue使用频率远低于其他语言这并非能力缺失而是设计使然errors用于非预期/异常情况用raise抛出、try/rescue捕获标准库通过foo/foo!双函数约定把是否视为异常的决定权交还给开发者throws仅用于无法以常规返回值表达流程的极少数场景exits是进程间通信与监督树容错的基础与其捕获它不如让监督者重启进程、回到已知状态after提供资源清理的软保证else在成功路径上继续模式匹配且变量作用域严格隔离。掌握这套机制你就能写出更符合 Elixir 哲学、更易被监督树托管容错的代码。接下来我们将进入 Elixir 的核心特性——进程Processes学习如何轻松编写并发、并行与分布式程序。赞分享编程语言编译器标准库语言运行时并发编程【免费下载链接】elixirSimple from zero to scale项目地址https://gitcode.com/GitHub_Trending/el/elixir点击查看免费下载相关推荐JavaScript 错误处理深入理解 try...catch 机制JavaScript 错误处理深入理解 try...catch 机制 前言 在 JavaScript 开发中错误处理是构建健壮应用程序的关键环节。本文将深入文档教程前端JavaScript 错误处理深入理解 try..catch 机制JavaScript 错误处理深入理解 try..catch 机制 为什么需要错误处理 在编程实践中无论我们多么优秀脚本中总会出现各种错误。这些错误可能源JavaScript 错误处理try...catch 机制详解JavaScript 错误处理try...catch 机制详解 前言 在编程过程中错误是不可避免的。无论是因为代码逻辑问题、用户输入异常还是服务器响应错误文档/教程前端上一篇多版本 Minecraft 服务器 Docker 部署5 分钟从零到联机整合包自动装好下一篇终极mcp-use Docker部署指南5个步骤容器化你的MCP应用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Claude Code MCP配置实战:从原理到避坑指南 2026/10/1 14:06:24

Claude Code MCP配置实战:从原理到避坑指南

1. 先搞清楚 MCP 到底是个什么东西1.1 从一次真实的抓狂经历说起上个月我在用 Claude Code 写一个 Node.js 项目,需要它帮我查一下 MySQL 里某张表的结构。结果它只能干瞪眼——因为它默认只能读写本地文件、跑跑终端命令,对外部世界一无所知。我当时就想…

阅读更多 →
FPGA入门必看:Quartus安装配置、工程创建与下载调试全流程指南 2026/10/1 14:06:24

FPGA入门必看:Quartus安装配置、工程创建与下载调试全流程指南

很多刚接触FPGA的朋友,第一次打开Quartus的时候都是懵的。界面密密麻麻的按钮,新建工程一堆选项,综合、布局布线、引脚分配、下载程序,每一步都有讲究。更麻烦的是版本还特别多,什么Quartus II 13.1、Quartus Prime 18…

阅读更多 →
PanWatch 提示词工程实战:4 个 prompts 模板如何塑造 AI 分析师人格 2026/10/1 14:06:24

PanWatch 提示词工程实战:4 个 prompts 模板如何塑造 AI 分析师人格

PanWatch 提示词工程实战:4 个 prompts 模板如何塑造 AI 分析师人格 【免费下载链接】PanWatch PanWatch — AI stock monitoring for A-shares, HK & US markets, powered by TradingAgents. Portfolio insights, real-time alerts & automated reports.&a…

阅读更多 →
办公自动化全栈实践:飞书+Office+Python深度集成 2026/10/1 14:06:24

办公自动化全栈实践:飞书+Office+Python深度集成

1. 这门课不是教你怎么点开飞书——而是帮你把办公软件变成“自动执行的代码”我带过三届全栈训练营,每次开课前都会做个小调查:问学员“你日常工作中最耗时、最重复、最想甩给机器干的三件事是什么?”结果惊人地一致:整理会议纪要…

阅读更多 →
深度配置@shadcn/lint:settings识别选项与组件导入匹配完全指南 2026/10/1 14:06:24

深度配置@shadcn/lint:settings识别选项与组件导入匹配完全指南

深度配置shadcn/lint:settings识别选项与组件导入匹配完全指南 【免费下载链接】lint An agent-first linter for Tailwind design systems. Write design system rules that agents can verify. 项目地址: https://gitcode.com/gh_mirrors/lint3/lint shadc…

阅读更多 →
MATLAB卷积神经网络手写数字识别:从原理到训练调参全攻略 2026/10/1 14:06:17

MATLAB卷积神经网络手写数字识别:从原理到训练调参全攻略

简介:基于MATLAB的卷积神经网络手写数字识别完整工程,面向深度学习初学者和MATLAB用户,以MNIST官方数据集为对象,完整覆盖了数据预处理、CNN模型搭建、迭代训练、测试评估和结果可视化等环节。压缩包共28个文件,总大小…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉