The file handle is the PID of the process that opened the file, it monitors the process that asked for the file to be opened. and if that process goes down the file will be closed.
You can also add an else in the with so it looks like
with {:ok, fh} <- File.open(filename),
{:ok, contents} <- IO.read(fh)
do
contents
else
{:error, reason} -> File.close(fh)
{:error, reason} # this will return after the file has been closed
end
or maybe you would prefer to open the file, and pass that into the with and either way close the file. I just used IO as an example because they return nice {:ok, x} or {:error, reason} tuples, but this works with any pattern.