在erlang中,只有throw
,没有raise
。 长生不老药中的raise
和throw
有什么区别?
错误生成的代码如下:
defp open_imu() do
{:ok, pid} = Circuits.UART.start_link()
# imu_port: "tty.usbserial-1410",
imu_port = Application.fetch_env!(Mechanics, :imu_port)
imu_speed = Application.fetch_env!(Mechanics, :imu_speed)
case Circuits.UART.open(pid, imu_port, speed: imu_speed, active: true) do
:ok ->
pid;
{:error,reason} ->
Logger.error("serial can not open")
throw(reason) # <----- if use throw, it is ok, if use raise, it is not ok.
end
end
使用raise
时,iex
生成以下消息,则无法捕获。
03:12:05.921 [error] serial can not open
03:12:05.923 [error] GenServer Mechanics.MechanicsImu terminating
** (UndefinedFunctionError) function :enoent.exception/1 is undefined (module :enoent is not available)
:enoent.exception([])
(mechanics 0.1.0) lib/mechanics/mechanics_imu.ex:91: Mechanics.MechanicsImu.open_imu/0
(mechanics 0.1.0) lib/mechanics/mechanics_imu.ex:35: Mechanics.MechanicsImu.handle_info/2
(stdlib 3.17.2) gen_server.erl:695: :gen_server.try_dispatch/4
(stdlib 3.17.2) gen_server.erl:771: :gen_server.handle_msg/6
(stdlib 3.17.2) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: :check_equipment
State: %{imuPid: nil, interval: 5000}
throw
更像是流控制,可以看作是早期返回,您可以在其中将任何值类型throw
到其他范围之外并停止当前函数。
raise
专门用于异常,其中某些内容失败,错误。您只能raise
例外,但有一些语法口红可以将字符串捆绑到异常中。
不能引发原子(除非原子是定义exception/1
函数的模块的名称。
iex(local@host)1> raise :some_atom
** (UndefinedFunctionError) function :some_atom.exception/1 is undefined (module :some_atom is not available)
:some_atom.exception([])
引发一些随机数据会给出有关允许类型的更清晰的错误:
iex(local@host)1> raise %{}
** (ArgumentError) raise/1 and reraise/2 expect a module name, string or exception as the first argument, got: %{}
引发字符串是围绕运行时错误的语法糖:
iex(local@host)1> raise "runtime error shortcut"
** (RuntimeError) runtime error shortcut
直接引发运行时结构:
iex(local@host)1> raise %RuntimeError{}
** (RuntimeError) runtime error
该模块本身也可以工作:
iex(local@host)1> raise RuntimeError
** (RuntimeError) runtime error
因为从技术上讲,它正在提高这个原子:
iex(local@host)1> raise :"Elixir.RuntimeError"
** (RuntimeError) runtime error
而对位点,throw
可以接受任何值:
iex(local@host)1> throw 10
** (throw) 10
iex(local@host)1> throw :atom
** (throw) :atom
iex(local@host)1> throw nil
** (throw) nil
值得指出的是,throw
是"由编译器内联"的,并且比raising
有更多的大脑并需要创建结构等要快得多。我认为这种差异可能是没有意义的,因为你不应该无缘无故地举起,而且通常有比投掷更好的模式,但在某些情况下,这将是有影响力的。
Generated throw_vs_raise app
Operating System: Linux
CPU Information: Intel(R) Core(TM) i5-4670K CPU @ 3.40GHz
Number of Available Cores: 4
Available memory: 23.37 GB
Elixir 1.13.1
Erlang 24.2
Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 0 ns
reduction time: 0 ns
parallel: 1
inputs: none specified
Estimated total run time: 14 s
Benchmarking raise ...
Benchmarking throw ...
Name ips average deviation median 99th %
throw 6.24 M 0.160 μs ±4661.27% 0.156 μs 0.190 μs
raise 0.91 M 1.10 μs ±43.02% 1.07 μs 1.48 μs
Comparison:
throw 6.24 M
raise 0.91 M - 6.84x slower +0.94 μs
Benchee.run(
%{
"throw" => fn ->
try do
throw "throw"
catch
t -> t
end
end,
"raise" => fn ->
try do
raise "raise"
rescue
r -> r
end
end})
如果足够疯狂,您可以将数据捆绑在异常中并将其用于流量控制,但不建议这样做,但到目前为止,它们都具有一些非常相似的行为,即您停止当前执行并将值传递到堆栈到其他地方,但是传递该值的内容和原因的语义是不同的。
在长生不老药中,raise
用于在代码中引发异常情况,例如
iex> :foo + 1
** (ArithmeticError) bad argument in arithmetic expression: :foo + 1
:erlang.+(:foo, 1)
为了捕捉提高的价值,您使用rescue
,像这样
iex> try do
...> raise "something went wrong..."
...> rescue
...> RuntimeError -> "Got an error!"
...> end
"Got an error!"
throw
的目的略有不同 - 当您(出于某种原因)必须抛出一个值以稍后捕获它时,会使用它。抛出并不一定意味着代码中发生了一些异常。它很少使用,通常是在使用未提供适当 API 的库时
要捕获抛出的值,请使用catch
iex> try do
...> throw "my cool value"
...> catch
...> x -> "Received #{x}"
...> end
"Received my cool value"
检查入门,一切都在那里描述