Contents

The &why keyword

Introduction

Classic Icon traditionally uses failure to indicate that a particular operation has not succeeded. For example, the builtin open function will fail if, for some reason, the given file cannot be opened. Unfortunately, this leaves the caller guessing as to why the operation didn’t succeed.

Object Icon uses a very simple way to improve things. A keyword &why is provided, which is really just a simple global string variable. A procedure or function performing an operation can, on failure, set this keyword to a string indicating the reason for failure. For example, consider the following interaction in ieval :-

$ ieval -i io
> open("nonsense.txt")
> &why
"No such file or directory (errno=2)"
> open("/root/nonsense.txt")
> &why
"Permission denied (errno=13)"
>

&why can be assigned to like any other variable. It is helpful when propagating errors, to keep any existing value of &why, and prepend a message, usually separated with a colon and a space (“:”). For example :-

procedure open_database(x)
   ...
   (f := open(x || ".db")) | {
      &why := "Failed to open database: " || &why
      fail
   }
   ...
end

Since assignment to &why is almost always followed by failure, there is a helpful utility procedure, util.error, which simply sets &why and fails. This would make the above code a little more concise :-

procedure open_database(x)
   ...
   (f := open(x || ".db")) | return error("Failed to open database: " || &why)
   ...
end

Another useful library procedure is ipl.printf.whyf. This takes a printf-style format and parameters and sets &why to the resulting string. The format “%w” can be used to insert &why into the result, so the above example using error() could be written as :-

   return whyf("Failed to open database: %w")

errno

C programmers will notice that &why is very similar to errno, the global variable by which various C library functions communicate error information. Indeed, in the ieval output given above, the values for &why come from turning errno into a string form. When errno is used in this way by Object Icon, the raw value of errno is retained by appending its value to the end of the string; thus rather than simply “Permission denied”, we set &why to “Permission denied (errno=13)”. This is occasionally useful when we wish to do something if failure occurred for a specific reason. To extract the raw errno value from &why, the utility procedure util.errno may be used, as follows :-

$ ieval -i io,util
> open("/root/nonsense.txt")
> &why
"Permission denied (errno=13)"
> errno()
13
>

The various constant values which errno may take can be found in the class posix.Errno. We could then use logic like :-

(f := open(s)) | {
   if errno() = Errno.EACCES then
      # Do something if permission denied (EACCES is 13).
   else
      # Any other reason
}

One point to note is that the presence of these constants is somewhat system dependent. Plan 9 for example, doesn’t use errno at all and uses a string-based error message system.

save_why

Occasionally it is useful to call a procedure, but to prevent it from altering the value of &why. This is easily achieved with the utility procedure util.save_why, which can be used as follows :-

# &why has some important value...
save_why{Files.remove(tmp_file)}
# &why still has the same value, regardless of whether `remove` failed.

save_why makes use of the technique described on this page.

Contents