Designing with Exceptions

If File.open succeeds, it returns {:ok, file}, where file is the service that gives you access to the file. If it fails, it returns {:error, reason}. So, for code that knows a file open might not succeed and wants to handle the fact, you might write

 case​ File.open(user_file_name) ​do
 {​:ok​, file} ->
  process(file)
 {​:error​, message} ->
  IO.puts ​:stderr​, ​"​​Couldn't open ​​#{​user_file_name​}​​: ​​#{​message​}​​"
 end

If instead you expect the file to open successfully every time, you could raise an exception on failure.

 case​ File.open(​"​​config_file"​) ​do
 {​:ok​, file} ->
  process(file)
 {​:error​, message} ->
 raise​ ​"​​Failed to open config file: ​​#{​message​}​​"
 end

Or you could let Elixir raise an exception for you and write

 { ​:ok​, file } = File.open(​"​​config_file"​)
 process(file)

If the pattern match on the first line fails, Elixir will raise a MatchError exception. It won’t be as informative as our version that handled the error explicitly, but if the error should never happen, this form is probably good enough (at least until it triggers the first time and the operations folks say they’d like more information).

An even better way to handle this is to use File.open!. The trailing exclamation point in the method name is an Elixir convention—if you see it, you know the function will raise an exception on error, and that exception will be meaningful. So we could simply write

 file = File.open!(​"​​config_file"​)

and get on with our lives.