Other answers already cover usage of raise vs. throw well.
I’ll describe the mechanics of how to handle each exceptional situation using a table:
creating | handling with | where y is
-----------------------------------------------------
raise x | rescue y | %RuntimeError{message: x}
error(x) | rescue y | %ErlangError{original: x}
throw x | catch y | x
exit(x) | catch :exit, y | x
where error(x) is actually :erlang.error(x).
On top of this, both rescue and catch/1 (catch with 1 argument) are just a syntactic sugar. All 4 cases above could be handled with catch/2:
creating | handling with | where y is | and z is
-----------------------------------------------------------------
raise x | catch y, z | :error | %RuntimeError{message: x}
error(x) | catch y, z | :error | x
throw x | catch y, z | :throw | x
exit(x) | catch y, z | :exit | x
Note the asymmetry of handling raise and error with rescue vs. catch/2: x is wrapped into %ErlangError when rescue is used, but not with catch/2.