Sun, 2 Feb 2003 17:59:49 -0000
> > > import IORef
> > > import IOExts
> > >
> > > globalVar :: IORef Int
> > > globalVar = unsafePerformIO $ newIORef 0
> > John Hughes wrote a nice pearl on the subject, see
> > http://www.math.chalmers.se/~rjmh/Globals.ps
> This paper claims ``unsafePerformIO is unsafe''. That's not actually
> true in the sense meant; unsafePerformIO merely has safety
> pre-conditions that the compiler can't check.
Which is the main sense in which the 'unsafe' prefix is usually meant
to be interpreted, and that is bad enough (see below, then re-read John's
quote of Simon PJ's description of unsafePerformIO;-). In particular, the
'unsafe'-prefix is not a hint for the implementation to treat something with
extra care, but a hint for the programmer that the implementation may shake
unsafe expressions around like any other ones (inlining, cse, ..), even though
that is bound to lead to problems with the hidden side-effects.
It is the programmer's responsibility to verify that none of these problems
matter in the particular case of usage. Since many advances in compiler
technology tend to invalidate those verifications, it is almost impossible to
guarantee safety in such cases - about the best one can hope for is to identify
and document precisely which assumptions need to be made to "guarantee"
safety. Unfortunately, this leaves it to users to figure out whether the assumptions
made by 'unsafe' authors (e.g., no inlining) are still valid at the point of use..
Btw, when talking about unsafety in that paper, John also happens to point
out the other little problem with unsafePerformIO: it permits to break type
safety (many a good spirit has stumbled over that "polymorphic references"
problem in other functional languages).
> The precondition (proof obligation) of unsafePerformIO is that the order
> in which unsafePerformIOs are performed cannot affect the outcome of the
> program. However, in this case, ordering doesn't matter: the only side
> effect is allocation of a new IORef, and IORefs are sufficiently opaque
> we don't care (or really know) about un-allocated IORefs while the only
> case we care about the now-allocated IORef is when we de-reference it.
> But, that forces the IORef, which executes the unsafePerformIO. So,
> whenever we access the variable, it is allocated. Therefore, the
> outcome of the program (regardless of the order of evaluation) is the
> same as if all such global variable declarations are executed before
> main begins executing. So, the outcome is independent of the order of
> There you go: the precondition of unsafePerformIO is satisfied, so the
> usage is safe.
There you went.. into one of the many available traps in this mine-field:
You argue that unallocated IORefs don't matter as long as "the" IORef
is allocated before it is dereferenced. But that's just part of the problem
- what about inlining globalVar, creating multiple IORefs? Remember
that, by using unsafePerformIO, you've given the compiler the license
to treat globalVar as an expression without side-effects, and for those
inlining is a common first step to enable further optimisations (last time
I checked, the language report didn't even guarantee the sharing on
which the globalVar trick depends)! Now there are multiple side-effects
instead of a single one, and read-and write-accesses are spread out over
the multiple copies of your IORef, none of which is likely to hold the
value you'd like it to have..