The correct way to handle readFile and writeFile exceptions

I am writing an application in Haskell and would like to show a meaningful error message to the user if readFile or writeFile fails. I now caught IOError using Control.Exception.tryJust and converted them to human readable text.

However, it is difficult for me to understand what errors I should catch and how to extract information from them. For example, if "/ bin" is a directory and "/ bin / ls" is a file, readFile "/bin" and readFile "/bin/ls/asdf" both give an "inappropriate type", but (in my opinion) they are different errors. In the case of the first, I was able to recover by processing each file in the directory, while the second is more like an error such as "does not exist".

In relation to the previous example, there seems to be no portable way to catch errors of the β€œwrong type”. Looking at GHC.IO.Exception , InappropriateType marked with GHC-only, so I can't just map the pattern by ioeGetErrorType . I could match the match on ioeGetErrorString , but I'm not sure if these strings are always the same for different platforms, compilers, locales, etc.

In conclusion, my questions are:

  • What exceptions should be used for readFile / writeFile ?
  • Once I have an exception, how should I extract information from it?
  • Is there a way to throw GHC exceptions like InappropriateType ?

Update:

Based on @ErikR's answer, I am looking at the GHC.IO.Exception.IOException fields with the following Haskell program:

 import Control.Exception (try) import GHC.IO.Exception (IOException(..)) import qualified Data.ByteString as B main :: IO () main = do try (readFile "/nonexistent") >>= printException try (writeFile "/dev/full" " ") >>= printException try (readFile "/root") >>= printException try (readFile "/bin") >>= printException try (writeFile "/bin" "") >>= printException try (readFile "/bin/ls/asdf") >>= printException try (writeFile "/bin/ls/asdf" "") >>= printException try (B.readFile "/dev/null") >>= printException -- I have /media/backups mounted as read-only. Substitute your own read-only -- filesystem for this one try (writeFile "/media/backups/asdf" "") >>= printException printException :: Either IOError a -> IO () printException (Right _) = putStrLn "No exception caught" printException (Left e) = putStrLn $ concat [ "ioe_filename = " , show $ ioe_filename e , ", ioe_description = " , show $ ioe_description e , ", ioe_errno = " , show $ ioe_errno e ] 

Output to Debian Sid GNU / Linux with GHC 7.10.3:

 ioe_filename = Just "/nonexistent", ioe_description = "No such file or directory", ioe_errno = Just 2 ioe_filename = Just "/dev/full", ioe_description = "No space left on device", ioe_errno = Just 28 ioe_filename = Just "/root", ioe_description = "Permission denied", ioe_errno = Just 13 ioe_filename = Just "/bin", ioe_description = "is a directory", ioe_errno = Nothing ioe_filename = Just "/bin", ioe_description = "Is a directory", ioe_errno = Just 21 ioe_filename = Just "/bin/ls/asdf", ioe_description = "Not a directory", ioe_errno = Just 20 ioe_filename = Just "/bin/ls/asdf", ioe_description = "Not a directory", ioe_errno = Just 20 ioe_filename = Just "/dev/null", ioe_description = "not a regular file", ioe_errno = Nothing ioe_filename = Just "/media/backups/asdf", ioe_description = "Read-only file system", ioe_errno = Just 30 
+5
source share
1 answer
  • What exceptions should I catch for readFile / writeFile?

On OS X, if you use openFile and then hGetContents instead of readFile , you will get different exceptions for the cases you mention.

openFile "/bin/ls/asdf" ... will throw an exception "there is no such file or directory", whereas openFile "/bin" ... will cause an "inappropriate type".

On Linux, both open calls throw an "inappropriate type" exception. However, you can distinguish between these two fields: ioe_errno and ioe_description :

 import System.IO import GHC.IO.Exception import Control.Exception foo path = do h <- openFile path ReadMode hClose h show_ioe :: IOException -> IO () show_ioe e = do putStrLn $ "errno: " ++ show (ioe_errno e) putStrLn $ "description: " ++ ioe_description e bar path = foo path `catch` show_ioe 

Example ghci session:

 *Main> bar "/bin" errno: Nothing description: is a directory *Main> bar "/bin/ls/asd" errno: Just 20 description: Not a directory 
  1. Once I have an exception, how should I extract information from it?

Each exception has its own structure. The definition of an IOException can be found here .

To bring field accessors to scope, you need to import GHC.IO.Exception .

  1. Is there a portable way to catch GHC exceptions like FriendlyType?

As @dfeuer said, for all practical purposes, the GHC is the only Haskell implementation currently.

Update

The results of running your program. I did not include the final result because I did not have a read-only file system to check it, but I am sure the error will be the same.

 ioe_filename = Just "/nonexistent", ioe_description = "No such file or directory", ioe_errno = Just 2 ioe_filename = Just "/dev/full", ioe_description = "Permission denied", ioe_errno = Just 13 ioe_filename = Just "/root", ioe_description = "is a directory", ioe_errno = Nothing ioe_filename = Just "/bin", ioe_description = "is a directory", ioe_errno = Nothing ioe_filename = Just "/bin", ioe_description = "Is a directory", ioe_errno = Just 21 ioe_filename = Just "/bin/ls/asdf", ioe_description = "Not a directory", ioe_errno = Just 20 ioe_filename = Just "/bin/ls/asdf", ioe_description = "Not a directory", ioe_errno = Just 20 ioe_filename = Just "/dev/null", ioe_description = "not a regular file", ioe_errno = Nothing 
+3
source

All Articles